Open Source Security

pnpm and Yarn Modern Lockfile Security

pnpm-lock.yaml and yarn.lock look similar on the surface but enforce different security properties. Here is what matters in 2026, and what still trips teams up.

Shadab Khan
Security Engineer
7 min read

The JavaScript ecosystem's lockfile situation is less uniform than it looks. npm, pnpm, and Yarn each produce lockfiles that do broadly similar things, but the details differ in ways that matter for supply chain security. pnpm and Yarn in particular have evolved specific design decisions over the last few years that make their lockfiles noticeably stronger artifacts than package-lock.json, and teams that still reach for npm out of habit are leaving real security properties on the table.

What does a lockfile actually need to do for security?

Three things at minimum. It needs to pin every dependency, direct and transitive, to a specific version. It needs to pin every package to a specific content hash, so that re-fetching the same version from a compromised registry cannot silently swap in different content. And it needs to capture the resolution context well enough that a fresh install from the lockfile produces the same dependency graph regardless of when or where it runs.

All three major lockfile formats do the first two. The third is where they differ. package-lock.json is structurally close to a dependency tree, which makes it readable but means the same intent can produce different lockfiles depending on install order and version of npm. pnpm-lock.yaml is explicitly designed to be deterministic and self-describing. Modern yarn.lock (Yarn 3 and later) in its Plug'n'Play or node_modules linker mode is comparably deterministic. The determinism differences show up in practice when you try to enforce "no drift" in CI.

Why does pnpm's content-addressable store matter for security?

pnpm stores packages in a single global content-addressable store and hard-links them into each project's node_modules. Every file that ends up on disk has a hash that matches what the registry advertised, verified at fetch time. When you install a project for the second time, pnpm does not re-fetch packages it already has; it just creates new hard links. This design has performance benefits that get most of the attention, but it also has a security property that gets less press: once a known-good package is in your store, serving it to a new project does not re-expose you to registry compromise for that specific version.

The practical implication is that pnpm's integrity model is closer to the Go module cache than to npm's traditional model. Your laptop or CI runner accumulates a trusted store over time, and attacks have to hit you at the moment of first fetch rather than on every install. Combined with strict frozen-lockfile settings in CI, this is a genuinely stronger posture than npm gives you out of the box.

What is the story with Yarn's hardened mode?

Yarn 3 and 4 have a "hardened mode" that makes lockfile violations in CI a fatal error, checks integrity hashes aggressively, and refuses to run lifecycle scripts from unverified sources by default. The mode is not on by default, because it breaks a nontrivial fraction of real-world projects that rely on looser behavior. For a new project, or for a project you are willing to spend a day normalizing, hardened mode is the single largest supply chain win available in the Yarn ecosystem.

The most common blocker to enabling hardened mode is packages that run install scripts that assume write access to the package source. Yarn's Plug'n'Play mode breaks these, and hardened mode refuses to work around them. The fix is either replacing the offending dependency or explicitly opting it into unplugging, which at least makes the exception visible in your configuration rather than silent.

How do overrides and resolutions interact with supply chain policy?

overrides in npm and pnpm, and resolutions in Yarn, let you force a specific version of a transitive dependency regardless of what the direct dependency declares. These are essential tools for responding to advisories when the direct dependency has not yet pushed a fixed version. They are also a source of supply chain risk when used carelessly.

An override pins a version, which is a security benefit, but it also bypasses the version ranges that the original author intended. If the override is too narrow, you miss out on later security fixes; if the override is too wide, you might resolve to a different major version with incompatible API. The pattern that survives contact with reality is to treat every override as temporary, with a ticket tracking the expected removal when the direct dependency catches up. Permanent overrides accumulate and eventually become the kind of technical debt that blocks major upgrades.

A subtler risk is that overrides can be added by anyone with commit access and do not necessarily show up in code review as "security-relevant changes." A new dependency override in a PR should trigger the same scrutiny as a new dependency declaration, which requires a culture where reviewers actually look at lockfile-adjacent changes rather than skimming past them.

How should CI enforce lockfile integrity?

The baseline is --frozen-lockfile (Yarn), --frozen-lockfile (pnpm), or npm ci for npm. All three refuse to modify the lockfile during install and fail if the lockfile is out of sync with package.json. Every CI build that installs dependencies should use the frozen mode, full stop. A surprising number of projects still use yarn install or pnpm install in CI, which silently mutates the lockfile if dependencies drift, and loses you the determinism guarantee in exactly the place you need it most.

Beyond frozen install, the next layer is verifying that the lockfile on disk matches what was committed. git diff --exit-code on the lockfile after a fresh install catches accidental mutations that the frozen flag might miss in edge cases. For pnpm specifically, pnpm install --frozen-lockfile combined with pnpm audit --prod gives you a clean baseline: no drift, no known vulnerabilities in production code paths.

What about lockfile linting and supply chain review tooling?

Several tools in the last couple of years have focused on lockfile diffing specifically for security review. lockfile-lint catches mismatched registries and unexpected sources. socket.dev and similar services annotate PRs with risk signals for new or changed dependencies. The value of these tools is highest on PR review, where the default diff view makes lockfile changes almost unreadable and reviewers tend to approve without looking.

Socket-style tools are not a replacement for policy; they are a signal layer on top of policy. Use them to surface the "you added a dependency maintained by a single account that registered last week" kind of finding, which is genuinely hard to catch otherwise. Do not use them as your only control; their false positive rate on common, legitimate dependencies is high enough that teams stop paying attention if they are not paired with coarser rules.

How do monorepos complicate lockfile security?

Monorepos concentrate lockfile risk. A single pnpm-lock.yaml or yarn.lock at the workspace root governs the dependencies of every package in the repo, which means a compromise of any dependency affects every package. pnpm's workspace-aware resolution and Yarn's workspaces feature both handle this correctly in the sense that they produce a coherent lockfile, but they do not change the blast radius.

The mitigations are policy-level. Enforce the same lockfile-integrity rules across every workspace package. Require that dependency additions name a specific workspace rather than being added to some default location. Review the full lockfile diff on every dependency PR, not just the package.json changes. And for very large monorepos, consider whether the security benefit of splitting into multiple independent lockfiles outweighs the operational cost of managing them separately. There is no universally right answer, but there is a very wrong answer, which is ignoring the question and letting a single lockfile govern hundreds of packages with no review discipline.

How Safeguard.sh Helps

Safeguard.sh compares your lockfile state against declared policy, flags drift in CI before it becomes a merge-conflict mess, and surfaces the specific dependency changes in each PR that warrant human review. We track override and resolution usage across your repositories so temporary pins do not quietly become permanent, and we integrate with the supply chain signal providers you already use so their findings show up in the review flow rather than a separate dashboard. The goal is lockfile hygiene that scales past the point where "look at the diff" is a realistic ask of your reviewers.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.