Talk to us
← All insights

Security

Check Where Your Production Builds Came From

A stolen publishing token can give malicious packages valid provenance. The TanStack attack reached npm with the release workflow's identity even though its publish step never ran.

In six minutes, 84 malicious versions across 42 TanStack packages reached npm using the project's trusted publishing identity. They carried valid provenance. Snyk called it "the first documented case of a malicious npm package carrying valid SLSA provenance".

The attacker had taken the release workflow's OIDC token from the runner. npm recognized that identity when the attacker published, so the packages received valid provenance.

How the attacker reached the token

Snyk traced the compromise to a pull_request_target workflow that let fork code poison a cache the release workflow later restored. When the release workflow ran its build, the poisoned code ran on the runner and extracted its OIDC token from memory before the workflow finished.

The publish step never ran. Tests failed, and the intended Publish Packages step was skipped. The stolen token let the attacker publish as the workflow anyway.

OIDC lets the publishing service recognize a workflow when issuing credentials. Here, the token was valid even though someone else was using it. A review of the publish step alone would miss how the attacker obtained that identity before publishing.

The cache connected jobs running at different times. Following the release's inputs back to the jobs that created them exposes that route from fork code to the runner carrying the publishing identity.

Check the signed record at deployment

The SLSA v1.0 provenance specification describes verifiable information about how an artifact was produced. It treats external build parameters as untrusted and requires downstream systems to verify them.

Make the deploy step check the signed record against the source repository and commit intended for release. For example, a build from an unapproved branch can still name the expected repository. The permitted workflow and branch also belong in the policy.

A check that accepts any valid provenance leaves those expectations unstated. Reject an artifact built through an unapproved workflow even if its record verifies.

Store the provenance you checked next to the deployed artifact. During an incident, the team can then recover the record accepted for that release.

For an application with several workflows, compare the policy with the job intended to produce production releases. A test workflow and a release workflow can live in the same repository. The deployment's allowed-workflow check distinguishes which path was authorized to produce the artifact being admitted.

Restrict the path that can publish

Snyk recommends these controls:

When the publishing workflow changes, check the trust policy at the publishing service. Moving a release to another branch changes which path that service is being asked to trust.

Let new dependency versions wait

Snyk says a seven-day cooldown would have blocked this attack. The wait keeps a fresh package release out of your build while there's time for a compromise to be discovered.

For a team that accepts automated dependency updates, the release's age can be checked before the version enters a production build. An update waiting in a pull request can remain queued until it reaches the agreed age.

An urgent update can justify an exception. The reason, recorded alongside the admitted version in the dependency change, explains why that release bypassed the normal waiting period.

Frequently asked questions

What does build provenance prove?

It gives verifiable information about where and how an artifact was built. A valid record can name the expected workflow while an attacker is using that workflow's stolen identity.

Did TanStack's release workflow publish the malicious packages?

Its intended publish step never ran. Snyk says the attacker extracted the workflow's OIDC token from runner memory and published with it, giving npm a valid trusted-publishing identity.

What should happen at deployment?

Check the signed record against the permitted source repository and commit, along with the expected workflow and branch. SLSA requires downstream verification of untrusted external parameters.

Why wait before accepting a dependency update?

A cooldown gives a newly published version time to be examined. Snyk said a seven-day wait would have blocked the TanStack attack; that doesn't make the wait a guarantee against other attacks.