Beyond Version Checks: Provenance and Behavioral Security
Lockfiles, cooldowns, and exclusion lists are necessary but not sufficient. Provenance verification and behavioral analysis catch the attacks they miss.
The first three posts in this series covered version-level controls: pinning tools, cooling off new releases, managing exclusions. Those controls reduce several common risks, but they do not inspect what a package does or prove that its build workflow was trustworthy.
Note
Fact-checked on 2026-07-16 against pnpm 11.10.0, npm’s registry-signature and provenance documentation, and the cited incident report.
In April 2026, the TanStack/router repository was compromised through a CI workflow manipulation1. The attacker published malicious packages with valid SLSA provenance, because the packages were genuinely built by the repository’s own pipeline. The pipeline’s internal state was compromised, but the build environment was legitimate. Provenance verified correctly. Version checks passed. The attack relied on behavioral patterns that no version-level control can catch.
Provenance Verification
What provenance tells you
When a package is published with --provenance on npm, the registry stores a Sigstore-signed attestation that links the package to:
- The source repository and commit
- The CI workflow that built it
- The build environment (GitHub Actions OIDC token)
This is roughly SLSA Build Level 22, the package was built on a hosted platform, and the provenance is cryptographically signed so it can’t be forged.
Registry signatures are separate from provenance
pnpm 11.1 added pnpm audit signatures. It verifies each installed package’s ECDSA registry signature against the public keys exposed by that registry at /-/npm/v1/keys. I added it as a CI step:
- name: Verify package signatures
run: pnpm audit signatures
A successful result establishes that the registry signature is present and cryptographically valid. It does not establish which CI workflow built the package, whether npm provenance exists, or whether the published code is benign. npm provenance is a separate signed attestation that links a package to source and build information. pnpm’s trustPolicy uses that trust evidence to detect a downgrade between releases.
What provenance doesn’t tell you
Provenance confirms which pipeline built a package, not whether that pipeline was clean. The TanStack/router incident demonstrated this: the attacker compromised the workflow itself, so the provenance was valid. The package was built by the right pipeline, but the pipeline was doing the wrong thing.
Provenance catches impersonation and token theft. It doesn’t catch pipeline compromise.
Behavioral Analysis
The install-time execution problem
Install time is a high-risk point in the npm lifecycle because lifecycle scripts execute package-controlled code with the permissions of the calling process.3 Depending on the environment, that process may be able to read:
~/.npmrc(registry tokens)~/.ssh/(SSH keys)GITHUB_TOKEN(in CI)- Environment variables (secrets, API keys)
allowBuilds in pnpm v11 blocks scripts from unreviewed packages. But for packages I’ve approved, the script runs with full privileges. If an approved package is later compromised (through a maintainer account takeover, for example), the next pnpm install executes the malicious script.
Socket.dev
Socket monitors packages for behavioral red flags4:
- Install-time network connections: a package that opens sockets during postinstall is suspicious
- Filesystem access: reading files outside the package directory (especially
~/.ssh,~/.npmrc) - Shell command execution: spawning child processes that aren’t part of the build
- Obfuscated code: dynamically decoded payloads, eval chains
- New binary introductions: a package that didn’t previously ship binaries suddenly adds one
Socket integrates with GitHub as a PR check. When a dependency update introduces a package with suspicious behavior, the PR gets flagged.
The TanStack lesson
The TanStack/router attacker had valid provenance. Version checks passed. The only signal was behavioral: the compromised pipeline was doing something it hadn’t done before5. Behavioral analysis catches this class of attack because it asks “what is this package doing?” rather than “is this package the right version?”
Private Registry Proxy
For projects with internal packages, a private registry proxy (Verdaccio, Artifactory, Cloudflare Workers) adds another gate:
- Route all npm traffic through the proxy
- Block known malicious versions at the proxy level
- Cache and audit every download
- Enforce organizational policies (no install scripts from untrusted sources)
This prevents dependency confusion attacks and gives a central point to enforce policies that package managers alone can’t.
SBOM Generation
A Software Bill of Materials (SBOM) is a machine-readable inventory of every dependency in your project. When a new vulnerability is disclosed, I can instantly check whether I’m affected.
Generate an SBOM with:
pnpm sbom --format spdx-json > sbom.json
For static sites this is low-risk. For anything that handles user data, an SBOM is the difference between “we’ll check” and “we know.”
The Full Stack
The layers from this series, in order of what to implement first:
| Layer | What it catches | Effort |
|---|---|---|
Lockfile + --frozen-lockfile | Unexpected version changes | Low |
minimumReleaseAge | Freshly published compromised packages | Low |
allowBuilds | Unreviewed install scripts | Low |
| SHA-pinned CI actions | Compromised mutable tags | Low |
pnpm audit in CI | Known vulnerabilities | Low |
| Renovate/Dependabot with cooldown | Stale dependencies, update velocity | Medium |
pnpm audit signatures | Missing or invalid registry signatures | Low |
trustPolicy: no-downgrade | Downgrades in available publication trust evidence | Low |
| Socket.dev | Behavioral anomalies in dependencies | Medium |
| Private registry proxy | Dependency confusion, central policy enforcement | Medium |
| SBOM generation | Impact analysis for new CVEs | Low |
No single layer is complete. Each one catches attacks that the others miss. The goal is to make supply chain attacks expensive enough that attackers move on.
What I’d Do Differently
I’d add pnpm audit signatures to CI earlier as an integrity check for registry signatures. I would document it beside provenance controls rather than treating it as provenance verification. I’d also evaluate Socket.dev for projects that handle user data because behavioral analysis covers activity that version and signature checks do not inspect.
The private registry proxy is the one I’d skip for a static blog. It’s worth the setup for anything with user-facing APIs or sensitive data, but the overhead isn’t justified for a site that only serves HTML.
References
- SLSA Framework. Supply chain integrity levels
- Sigstore. Keyless signing and verification for npm packages
- pnpm
audit signatures. ECDSA registry-signature verification and exit behavior - npm registry signatures. The
dist.signaturesand registry-key model verified by pnpm - npm provenance. Separate signed attestations for source and build information
- Socket.dev. Behavioral analysis for npm packages
- TanStack/router incident analysis. Case study of provenance-valid attack
- SBOM generation. Machine-readable dependency inventory
- StepSecurity. CI-level cooldown and compromised package detection
Footnotes
-
The full incident analysis is Unit 42’s April 2026 investigation. The key finding: the attacker compromised the CI workflow definition itself, not a maintainer token. This meant the build, signing key, and provenance attestation were all legitimate; the pipeline was simply building the wrong artifact. ↩
-
SLSA Build Level 2 requires the build to run on a hosted platform with signed provenance. It does not verify that the build pipeline’s source code or workflow definition is untampered; that’s Build Level 3. ↩
-
npm install scripts run with the permissions of the calling user and process. The npm lifecycle documentation lists
preinstall,install, andpostinstallas the scripts that run during package installation. In CI environments, this often means access to secrets and deployment credentials. ↩ -
Socket’s detection methodology categorizes risks into supply chain, quality, maintenance, license, and vulnerability signals. Install-time execution and network access are flagged as critical behavioral indicators. ↩
-
The Unit 42 report notes the behavioral anomaly was the tell: the compromised package performed network requests during installation that the legitimate version never did. Behavioral analysis tools like Socket detect these anomalies by comparing a package’s behavior against its historical baseline. ↩
This post was written with AI assistance.