Software Supply Chain Security Checklist

Chapter 1 of 9 · 6 checks · 4 min

Dependencies and lockfiles

Checks that every build installs exactly the dependency tree you reviewed, and that installing it cannot quietly run code you never approved.

Show
  1. Every project commits a lockfile that pins the full dependency treeMust

    Why it matters and how to check

    A manifest with ranges such as ^4.2.0 lets each install pick whatever version is newest that day, so two builds of the same commit can contain different code. A lockfile (package-lock.json, pnpm-lock.yaml, yarn.lock, poetry.lock, uv.lock, go.sum, Cargo.lock) records exact versions and integrity hashes for direct and transitive dependencies. OpenSSF Scorecard's Pinned-Dependencies check looks for exactly this.

    To verify: list every directory with a manifest and confirm a lockfile sits next to it in version control. Check .gitignore does not exclude lockfiles, and that lockfile changes show up in pull request diffs rather than being regenerated silently during builds.

  2. CI installs from the lockfile and fails if it does not matchMust

    Why it matters and how to check

    A lockfile only helps if CI refuses to change it. The npm docs describe npm ci as a clean install that exits with an error when the lockfile and package.json disagree, instead of updating the lockfile. Other tools have the same mode under a different flag.

    To verify: read the install step in every workflow. It should use the frozen form for its package manager. Then edit package.json on a branch without updating the lockfile and confirm the pipeline fails.

    Example

    Shell
    npm ci                              # npm
    pnpm install --frozen-lockfile      # pnpm
    yarn install --immutable            # Yarn Berry
    uv sync --locked                    # uv
    pip install --require-hashes -r requirements.txt
    cargo build --locked                # Rust
    go mod verify                       # Go: checks the module cache against go.sum
  3. Python installs use hash-checking mode or a tool lockfile with hashesShould

    Why it matters and how to check

    pip's secure installs guide describes hash-checking mode: with --require-hashes, every requirement, including transitive ones, must be pinned with == and carry a --hash, and pip refuses anything that does not match. The same guide suggests --only-binary :all: for a stricter setup, because building a source distribution can run arbitrary code.

    To verify: open the requirements file used for production. Every line should have a pinned version and at least one sha256 hash, usually generated by a tool such as pip-compile --generate-hashes or a lockfile from uv or Poetry. Run the production install command and confirm it includes --require-hashes or the locked mode of your tool.

  4. Dependency install scripts run only for packages you approvedMust

    Why it matters and how to check

    Lifecycle scripts such as preinstall and postinstall run with the permissions of whoever installs the package, including a CI job holding secrets. They have been the delivery route for many malicious npm releases. GitHub's changelog for npm 12 says npm install no longer runs preinstall, install or postinstall scripts from dependencies unless they are explicitly allowed, and that Git and remote URL dependencies are also off by default.

    To verify: check the npm version in CI with npm --version. On npm 12, read the allowScripts entries in package.json and confirm each approved package really needs a build step. On older npm, or other package managers, check whether CI sets --ignore-scripts or the equivalent and runs approved builds explicitly. Nobody should set dangerously-allow-all-scripts except as a short migration step.

    Example

    Shell
    # npm 12: see which dependencies want to run scripts
    npm approve-scripts --allow-scripts-pending
    
    # approve the ones that need a native build, block the rest
    npm approve-scripts esbuild sharp
    npm deny-scripts some-telemetry-package
  5. Each new direct dependency is checked before it is addedShould

    Why it matters and how to check

    The OpenSSF Concise Guide says to evaluate software before selecting it as a direct dependency and to double-check its name to counter typosquatting. A lookalike name (reqeusts, colours-js) or a package published last week by an unknown account is a common way in.

    To verify: pick the last five dependencies added to the repository and check that someone confirmed the exact name, the source repository, the maintainers, recent activity and whether the code is really needed. Make this a pull request template question for any change that touches a manifest.

    Do

    • Compare the package name character by character with the project homepage
    • Check the OpenSSF Scorecard result for the package repository when there is one

    Avoid

    • Adding a dependency for a few lines of code you could write yourself
  6. Production dependencies come from the registry, not Git branches or tarball URLsShould

    Why it matters and how to check

    A dependency pointing at a Git branch or a remote tarball has no registry integrity record and can change under you. The npm 12 changelog turns Git dependencies (--allow-git) and remote URL dependencies (--allow-remote) off by default for the same reason.

    To verify: search the manifests and lockfiles for git+, github:, http:// and https:// in dependency versions. Each hit should be replaced with a published version, or pinned to a full commit hash with a comment explaining why it cannot come from the registry.