Skip to content
Insights · Engineering

Name the package the lockfile pinned

Name the version the lockfile resolved, show the manifest range is not that version, and show another dependency still exact.

Scroll
EngineeringOct 20, 202613 min readBy Salman Naqvi, Founder & CEO
Name the package the lockfile pinned

A dependency is in the client's build. Tonight, three proofs, in a repository you or the client can open without the person who added it. Name one direct package, the range the manifest allows, and the version the lockfile resolved, including the integrity or the resolved address, or write which of those is missing. Show that the range is not that exact version, or show a policy refuse an install that has no lockfile. Then show a different dependency whose manifest and lockfile name the same exact version, and show that install still completes. If any of the three needs the author, you have not named a pin. You have a line someone remembers.

This is the version a fresh install will fetch. It is not the license text. Name the license the build added is the identifier, the text you can open, and whether a copy of the client's product leaves the building. A lockfile that names a version can still sit next to a missing license, and a license you have read does not say which version will install tomorrow. It is not the image the client is running. Name the commit the client is running is that digest. A digest is not a package version. It is not the list of hosts the build calls. Name every host the build calls is that list. A registry hostname can appear on both pages, and the version is still a different field. It is not the key the build minted. Revoke the key they minted is the live credential. A pin does not rotate it. Who was allowed to merge the change is name who merged their change. An approval is not a resolved version.

None of what follows is a measurement of this client's tree, and none of it is a legal opinion about a license. The study below counted the public npm registry through 2018. The two npm pages describe what a lockfile and a range mean in the tool's own documentation now. They do not tell you the version your production install resolved last Tuesday, unless you open the file. They do not replace the lawyer when the question is whether the text allows the copy to leave.

The study that counted how wide a single package's trust runs is Zimmermann, Staicu, Tenny, and Pradel, USENIX Security 2019, 14–16 August, Santa Clara. npm, they write, had grown to over 800,000 packages as of February 2019. The dependency graph they actually computed is not that sentence. It has 676,539 nodes and 4,543,473 edges, built from registry metadata, without installing every package. Edges are regular dependencies: package pi depends on package pj. At the end of the measurement, 199,327 maintainers. The ratio of packages to maintainers stayed between 2.81 and 3.51. 676,539 is not "over 800,000." 199,327 is not 676,539. 2.81 is not 3.51. 4,543,473 is not a count of packages. Public npm in that window is not this client's private repository (Zimmermann, Staicu, Tenny, and Pradel, Small World with High Risks).

An average install trusts more code than the line someone typed, and the paper prints two averages that are not the same sentence. Direct dependencies of an average package rose from 1.3 in 2011 to 2.8 in 2018. Transitive dependencies reached what they call a staggering 80 in 2018. The findings list says installing an average package introduces an implicit trust on 79 third-party packages and 39 maintainers. 79 is not 80. 39 is not 79. 2.8 is not 80. 1.3 is not 2.8. The transitive count is why a range you did not type can still move the tree. The 39 is maintainers, not versions. A toy graph in their Figure 1 has an average of 5/6, which they write as 0.83, on six packages. 0.83 is that drawing. It is not the 2018 ecosystem.

How concentrated the trust is, they answer with several different cuts, and the cuts do not substitute. The introduction says about 140 trusted maintainers, out of a total of more than 150,000, could halve the risk from compromised maintainer accounts, and that trusting the top 300 packages reduces the risk by half. The body says the graph ended at 199,327 maintainers. 199,327 is more than 150,000, and it is not a replacement for that phrase. The body also says vetting around 140 maintainers cuts the average number of implicitly trusted maintainers in half, and vetting around 600 could bring that average below five. Half is not "less than five." 600 is not 140. Vetting the most depended-upon 1,500 packages would reduce implicitly trusted packages tenfold, and vetting 4,000 would reduce them by a factor of 25. 300 is not 1,500 is not 4,000. Half is not tenfold is not 25. Do not pick the smallest integer and call it the mitigation.

The vulnerability sentences are a band, not a point, and they are not a pin rate. The findings list says up to 40% of all packages depend on code with at least one publicly known vulnerability. The body says that for the majority of the time, the reach of vulnerable unpatched code sat between 30% and 40%, and then it repeats "up to 40% of all packages rely on code known to be vulnerable." Between 30 and 40 is not the single cell 40, and "up to 40" is not a measurement that your tree is at 40. They plot implicitly trusted packages against vulnerabilities and report a Pearson correlation of 0.495. 0.495 is not 40%, and it is not a probability for one package. They also say the common practice of locking dependencies can keep an application on a vulnerable release after a fix exists. A pin is a version you can name. It is not a patch. A lockfile that is a year old is a choice to stay, which you can defend only after you have read the version.

What they mean by a lock, in 2019, is narrower than a feeling that the build is reproducible. Dependencies are declared in package.json as a specific version, which they call locked, or as a range. Each install resolves the range to a specific version. The same package, on two machines or on two days, can download different versions when nothing pins the resolution. package-lock.json, as they describe it, locks each package in the tree to a specific version until a new lockfile is generated. That description is the paper's. It is not npm's current format note, and it is not a promise that a lockfile committed last year still matches the registry's tarball. If the lockfile is absent, you do not have their "until a new lockfile is generated." You have the range.

The current file format is a different page, and it is the one you open tonight. npm's documentation says package-lock.json "describes the exact tree that was generated, such that subsequent installs are able to generate identical trees, regardless of intermediate dependency updates." It is meant to be committed. It cannot be published, and it is ignored anywhere except the root. If package-lock.json and npm-shrinkwrap.json are both in the root, the shrinkwrap "will take precedence and package-lock.json will be ignored." A second file that wins is a fact you write down, not a footnote. lockfileVersion 1 is npm v5 and v6. 2 is v7 and v8. 3 is v9 and above. No version field is an "ancient" shrinkwrap from before npm v5. 3 is not version 3.0.0 of a dependency. The hidden lockfile at node_modules/.package-lock.json is lockfileVersion 3, and it is ignored unless it matches the tree that was just written. It is not the file you commit (npm, package-lock.json).

Two fields on that page are not the version, and people treat them as if they were. integrity is a sha512 or sha1 of the artifact that was unpacked. resolved is where it was fetched from: a tarball URL, a git URL with a commit, or a link target. registry.npmjs.org, the page says, is "a magic value meaning the currently configured registry." A magic value is not a pin to one host. An integrity string that you have not compared to the installed bytes is a claim. The version field is the version. If you cannot see it, the other two fields do not fill it in. A host the build calls can still be the registry, and that list remains the other article. The string registry.npmjs.org on a lock line does not answer it.

The range the manifest allows is its own page, and the examples do not all mean the same ceiling. npm's semantic versioning note says a patch range can be written 1.0, 1.0.x, or ~1.0.4. A minor range can be written 1, 1.x, or ^1.0.4. A major range can be written as a star or as x. The page introduces that list as syntax for ranges "up to 1.0.4," and the major bullet is a star, which is not a ceiling at 1.0.4. Do not let the introductory clause redefine the star. The example manifest uses ^1.0.0 and ~2.2.0. A caret is the minor bullet. A tilde is the patch bullet. Neither is the exact version 1.0.0, and neither is whatever the lockfile resolved last Tuesday. They recommend starting a package you publish at 1.0.0, then incrementing the third digit for a patch, the middle digit for a minor, and the first digit for a break. That recommendation is about versions you publish. It is not the version your install resolved (npm, About semantic versioning).

Do the three proofs in the repository you administer, on one install you can re-run. First, the name. Pick a direct dependency a human typed. Read the range in the manifest. Read the version, the integrity, and the resolved value in the lockfile you commit, not the hidden file under node_modules. If the shrinkwrap is the file npm will obey, read that one and write that the other file is ignored. If a field is missing, write missing. Second, the gap or the refusal. If the manifest is a caret, a tilde, a star, or an x, write the range next to the resolved version and do not call them the same string. If a policy fails the install when the lockfile is absent, show that failure on a copy, not by deleting the lockfile on the branch the client ships. Third, the exact neighbor. A different dependency whose manifest is one version and whose lockfile is that same version still installs. If you cannot find one, say so. An entire tree of ranges is a result. It is not the third proof. If you delete the lockfile and reinstall in order to "see what you get," you have generated a new resolution. You have not shown the one the client is about to ship.

Three moves feel like a pin and leave the resolution free. Committing package-lock.json and then listing the same packages with stars in a second manifest the install actually reads. Editing the version field by hand and leaving the integrity pointing at the old tarball, which is a file that no longer describes one artifact. Asking the model that suggested the import to "pin it," which may add a line to a prompt and leave both files. The license text, if you still have not opened it, remains the license article. Cleaning the range does not open the text. A secret in history is name the secret the history holds. A token in an old commit is not a resolved version.

The shape that makes one package findable is a product that already searches across repositories, not a slide of logos. Haystack is a search layer across documents, data lakes, and repositories. The shape that matters here is that a person can open a repository and find a file. That case is not a lockfile ledger. Nothing in the description is a headcount, a duration, or a result figure. If the only way to know the version is to ask the person who typed the import, you do not have the search. You have a memory.

There is a version of this afternoon where hiring a pin pass is the wrong purchase. The work is not sold, so there is no client-facing tree to stand behind. The client has asked to meet the people writing the code. That is a disclosure decision, when to tell a client you subcontract, not a reason to skip the version. The room only has a slide. There is no repository to open. The buyer only wants a template of package names with no lockfile under them. Or the client has already told a lawyer to decide, and what they asked you for is a legal opinion. Hire the lawyer. A lockfile does not issue opinions, and neither does this page.

When the build is real and the version is not on a screen you can open, the missing object is specific. The manifest, the lockfile the installer will obey, and one exact neighbor that still installs. A white label software development partner that expects this will put the repository in the client's organization, or in yours, before the first install, because a range resolved on a laptop you do not hold is not a version you can name. One that can only paste a dependency list into chat is telling you who holds the tree. Where the next slice is one capability and the acceptance sentence names the resolved version, an AI integration sprint is the shape that can require the three proofs as the definition of done. Where you are not sure which repository the current engagement installs from, a two-week AI readiness assessment maps the systems. The findings are yours. They are not a promise that 40% of this tree is vulnerable, and they are not a rate.

A sign-in you can show, and a model identifier on one request, are different stops: show the login they did not make and name the model the request hit. Do not record either on this page. An authentication event is not a package version. A model id is not a lockfile.

Before the branch ships, write the three lines. The package, the range, and the version the lockfile resolved, or the field that is missing. The range is not that version, or the install without a lockfile is refused. A different dependency is exact, and it still installs. Do not delete the lockfile and call the new tree the old one. The range is what you allowed. The lockfile is the page you can hand the client. The exact neighbor is the proof you did not only forbid.

Find this useful? Tell Google to show you more of it.

Let's put AI to work in your business.

A 30-minute call. You bring the workflow or the roadmap — we'll tell you what's feasible, what it costs, and what we'd build first.

Book a call→