# Build script produces output

- **Pattern:** `ab-002380` (`sdk-package-quality.build-distribution.build-script`)
- **Severity:** medium
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-002380
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-002380)

## Why it matters

A missing `prepublishOnly` script means a developer can run `npm publish` against stale or missing `dist/` output — shipping a broken release to every consumer before anyone notices. ISO 25010 fault-tolerance requires that the release process itself catch this class of error. SLSA L1 requires that the build is reproducible and that the published artifact reflects the declared source version. A manual build-then-publish workflow is an invitation for version skew between source and registry.

## Severity rationale

Medium because the gap between build and publish is a procedural failure mode that produces broken releases — not a security issue, but a reliability one that affects all consumers of the broken version.

## Remediation

Add a `prepublishOnly` script that guarantees a fresh build runs before every `npm publish`.

```json
{
  "scripts": {
    "build": "tsup",
    "prepublishOnly": "npm run build"
  }
}
```

`prepublishOnly` runs automatically before `npm publish` (not before `npm install`). For packages that need CI-only publishing, also add a `prepack` hook — it runs before both `npm pack` and `npm publish`. Confirm the build output directory in `scripts.build` matches the paths in `main`, `module`, and `exports`.

## Detection

- **ID:** `build-script`
- **Severity:** `medium`
- **What to look for:** List all scripts in package.json. For each build-related script, check `package.json` `scripts` for a `build` script (or equivalent):
  1. A `build` script exists and invokes the detected build tool
  2. A `prepublishOnly` script that runs the build before publishing
  3. The build output directory (`dist/`, `lib/`, `build/`) exists or would be created by the build script
  4. The `main`/`module`/`exports` paths reference the build output directory
  For Python: check for a build command in `pyproject.toml` or `Makefile`. For Rust: `cargo build` is implicit.
- **Pass criteria:** A `build` script exists in `package.json` that invokes the build tool, AND a `prepublishOnly` script exists that ensures the build runs before `npm publish`. The output directory referenced by `main`/`exports` would be produced by the build — at least 1 build script must be configured (build, prepublishOnly, or equivalent). Report: "X build scripts configured."
- **Fail criteria:** No `build` script, OR no `prepublishOnly` script (risk of publishing stale/missing build output), OR the build script exists but its output directory doesn't match what `main`/`exports` reference.
- **Skip (N/A) when:** The package publishes source directly (no build step) — e.g., a pure JavaScript package with no transpilation. Also skip for Go (no build artifact for libraries) and Python packages that use only `pyproject.toml` with a standard build backend.
- **Detail on fail:** `"Build script runs 'tsup' but there is no prepublishOnly script. If a developer runs 'npm publish' without building first, stale or missing dist/ files will be published."`
- **Remediation:** Always ensure the build runs before publishing. The `prepublishOnly` script runs automatically before `npm publish`:

  ```json
  {
    "scripts": {
      "build": "tsup",
      "prepublishOnly": "npm run build"
    }
  }
  ```

  Some teams also add a `prepack` script (runs before `npm pack`, which `npm publish` calls internally). Either works.

## External references

- iso-25010 reliability.fault-tolerance
- ssdf PS.3.1
- slsa L1

Taxons: operational-readiness

HTML version: https://auditbuffet.com/patterns/ab-002380
