skip to content

Pinning & Lockfile Integrity

A version range is a standing trust decision renewed on every install, while a lockfile with integrity hashes freezes it. Interviewers probe it because a lockfile edit can hide a substituted package.

on this pageshow

questions

4

Why does changing one line inside a committed lockfile count as a code change?

level: juniorimportance: must knowfreq 60%

answer

  1. intent versus outcome
  2. the file installs actually read
  3. one line names the bytes
  4. installs can execute package scripts
  5. no source diff for a reviewer

basics

~20 s

A lockfile line names the exact package version, the source it is fetched from and the bytes accepted as that release. Editing it swaps different executable code into the build, with no source-file diff to review.

solid answer

~50 s

A manifest states intent - which packages you want and roughly which versions. A lockfile states the outcome: for every direct and transitive package, the exact version, where the bytes come from, and usually a hash of those bytes. Installs read the lockfile, so each entry decides what actually lands on disk and runs. That code is not inert: several ecosystems execute package-supplied scripts at install time, and everything else gets imported and runs in production with whatever credentials the process holds. So a one-line lock edit can introduce a different package, a different release of the same package, or the same version number backed by different bytes - all without touching a single source file. The practical consequence is that lockfiles are committed and reviewed like code, and a lock diff with no corresponding manifest change is a question, not formatting noise.

go deeper

for a junior

Be ready to say what a lockfile entry pins - exact version, source location and byte hash - and that installing a package can execute code on the machine doing the install.

for a middle

Explain which field in an entry changes what, and why a lock change with no manifest change deserves an explanation rather than an approval.

for a senior

Show how you keep lock diffs reviewable in real pull requests: refreshes in their own commit, attribution to a cause, and attention aimed at new packages and hash-only changes.

for a principal

Own the policy position that dependency changes are code changes: who may author a lock edit, what may auto-merge, and how review attention is rationed across many repositories.

## Manifest versus lockfile A dependency manifest records *intent*: the packages a project declares and the version constraints it will accept. A lockfile records the *outcome* of resolving that intent: every package in the graph, direct and transitive, with an exact version, the location the bytes were fetched from, and in most modern formats a cryptographic hash of those bytes. Installs on developer machines, in CI and in release builds read the lockfile. That is what makes it security-relevant rather than bookkeeping. ## Three fields, three different powers A typical entry carries at least three pieces of information, and each one changes something different: | Field | What changing it does | |---|---| | version / revision | a different release of the package runs | | resolved source | the bytes come from a different server | | integrity hash | different bytes are accepted as that same release | The third is the one people miss. A hash change with the version untouched means the same version string will now be satisfied by different content. Nothing in the version number tells you that. ## "Code" is meant literally Installing a package is not always an inert copy. Several ecosystems run package-supplied code at install time - lifecycle scripts, source builds, plugin hooks - which means the code runs before any test, before any scanner, and on whichever machine ran the install, including a developer laptop holding cloud credentials. Even where install is inert, the fetched code will be imported by the application, linked into the artifact, and executed in production with the same privileges as the code your team wrote. There is no privilege boundary between your first-party code and the dependency it imports. ## The review asymmetry attackers rely on A pull request that changes twelve lines of application code gets read line by line. The same pull request that also refreshes nine hundred lines of lockfile gets scrolled past, and many review interfaces collapse the file as generated. Reviewer attention is the scarce resource in every organisation, and the lockfile is where the ratio of bytes-to-attention is worst. That is exactly why a single altered entry buried in a large refresh is a realistic delivery route for hostile code from someone who already has legitimate contributor access. ## What good practice looks like - **Commit the lockfile and review it.** Do not gitignore it and do not treat it as build output. - **Attribute every lock change.** A refresh should be explainable: this manifest change, or this scheduled refresh, caused this resolution change. A lock diff with no manifest change and no known refresh is a question. - **Classify the diff rather than reading it linearly.** The interesting categories are: packages appearing that were not in the graph before, hashes changing while versions do not, resolved sources changing host, and packages that execute code at install time. - **Keep refreshes separate from behaviour changes.** A commit that both refreshes the lock and edits application logic is much harder to review than two commits. - **A new transitive package is a new trust decision.** It brings a new maintainer, a new publishing credential and new install-time code into your build. A patch bump of a package you already trust is a smaller decision, though not a free one. ## The misreading to avoid Many engineers answer "the integrity hash protects me, so a lockfile edit is safe." That gets the direction of the guarantee backwards. The hash tells you that the bytes fetched match what the lockfile *asked for*; it does not tell you that the lockfile asked for the right thing, and anyone who can edit the version can edit the hash in the same commit. The lockfile is the statement of trust; the hash only enforces it faithfully.

  • A lock diff adds three packages nobody recognises and no manifest file changed. What do you do?
    Stop and explain the change before approving. A resolution cannot move on its own, so either an upstream package widened its own dependencies, someone hand-edited the lock, or the refresh was generated somewhere other than a clean checkout. Identify which parent pulled each new package in, check what each one does at install time, and require the refresh to be reproducible from the manifest before it merges.
  • Why is a brand new transitive package a bigger decision than a patch bump of one you already have?
    A new package adds a new maintainer, a new publishing credential and new install-time code to the set of parties who can execute code in your build. A patch bump ships new code too, but inside a trust relationship you already accepted and under an account you already depend on. The first expands the trust surface; the second exercises it.
  • Is it enough to review the manifest and let the lockfile follow automatically?
    No. The manifest only shows the packages you declared. Most of the graph, and therefore most of the code that runs, appears only in the lockfile, and a resolution change can move dozens of packages you never named. Reviewing intent while ignoring outcome means never looking at the majority of the code you ship.

The manifest is the shopping list; the lockfile is the receipt naming the exact item, the exact shop and the exact weight. Altering the receipt changes what actually gets delivered.

saying these in an interview costs you the question

  • Calls the lockfile generated noise that needs no review
  • Assumes only the manifest matters because the lock just mirrors it
  • Believes an installed package cannot run code until it is imported
  • Says the integrity hash makes any lockfile edit harmless
  • Trusts a bot-authored refresh purely because a bot opened it

context

open as a page

If a lockfile entry's integrity hash and source URL are both edited, why does install-time verification still pass?

level: middleimportance: must knowfreq 52%

basics

~20 s

The hash is compared with the bytes fetched from the source named in that same entry. An attacker who edits both simply records a hash matching their own payload. Lockfile hashes detect tampering in transit, not a tampered lockfile.

open as a page

CI installs your Python requirements with hashes required, but developer laptops do not. Why is that drift a security finding?

level: seniorimportance: should knowfreq 44%

basics

~20 s

Only the enforced path is protected. A laptop installing without hash checks accepts whatever a compromised mirror or proxy serves, and the next regeneration writes those bytes' digest into the repository - which CI then enforces faithfully forever.

open as a page

Bots open lockfile-refresh pull requests across 400 repositories daily. How do you make regeneration a re-trust event?

level: principalimportance: nice to knowfreq 34%

basics

~20 s

Split each refresh into what a machine can reproduce and what it cannot. Auto-approve lock changes CI regenerates byte-for-byte from the manifest; send only the residue - hash-only changes, new packages, moved sources - to a named human.

open as a page