
The ChainDrop npm Worm: Reproducing the OIDC Trust Boundary That Allowed Mass Package Publication
What matters to me here is not the worm label. It is the trust boundary.
If the public report is right, ChainDrop did not need to break GitHub Actions OIDC. It used the normal path: a workflow identity token, a publish policy that trusted that identity, and a registry flow that accepted the resulting attestation as legitimate. That is exactly why this kind of incident is nasty. The mechanism looks clean on paper and still turns into a supply-chain poison pill once the workflow itself is compromised.
What the public report claims about ChainDrop
The reported scale, target, and outcome
The public report says ChainDrop was an npm worm that used GitHub Actions OIDC to poison 444 packages and still produced valid SLSA provenance for the published artifacts.
That combination is the part that stands out:
- npm packages were published from an automated pipeline
- the pipeline used GitHub Actions OIDC
- the published artifacts carried provenance that looked valid
- the report describes the campaign as worm-like, which suggests it spread through package or workflow trust rather than a one-off manual release
I have not independently verified the original investigation, the package list, or the exact propagation path. So I am treating the report’s scale claim as reported, not established fact.
What is confirmed versus what still needs primary-source verification
| Claim | Status | Why I treat it that way |
|---|---|---|
| GitHub Actions can mint short-lived OIDC identity tokens | Confirmed by GitHub docs | This is a documented platform capability |
| SLSA provenance can describe how a package was built | Confirmed by SLSA docs | Provenance is a build attestation, not a policy engine |
| ChainDrop used GitHub Actions OIDC | Reported in the public write-up | I did not verify the original report or registry metadata here |
| 444 packages were affected | Reported in the public write-up | Large-scale claim; needs the source investigation to confirm |
| Valid provenance prevented abuse | False, based on the report’s allegation | The whole point is that provenance did not stop the publication |
That split matters. The mechanism is documented; the incident details still need the original reporting or registry evidence.
Why GitHub Actions OIDC is the trust boundary that matters
How an OIDC token is supposed to prove workflow identity
GitHub Actions OIDC is meant to let a workflow request a short-lived identity token instead of storing a long-lived secret. In the normal case, the token says, in effect, “this exact workflow run on this exact repo/ref/environment asked for a credential.”
GitHub documents the claims that matter and the need to lock them down: sub, aud, repository, ref, workflow, job_workflow_ref, and sometimes environment. The registry or trust provider should check those claims against an allowlist, not just accept that “the token came from GitHub.”
That distinction is easy to miss in reviews. A token from GitHub is not automatically trusted. A token from the right workflow, branch, and environment might be.
Where package publishing becomes a security decision, not just automation
This is where people get sloppy.
npm publish in CI is not just a build step. It is an authorization decision that says:
- this artifact is ready for release
- this workflow is allowed to release it
- this identity is acceptable to the package registry
If the workflow context is compromised, the publish step becomes the attacker’s release mechanism. That is why I do not treat “it published with valid provenance” as a defense by itself.
A valid provenance attestation can prove where an artifact came from. It does not prove that the workflow should have been trusted to publish it in the first place.
Reconstructing the attack path without reproducing the malware
From compromised workflow context to package publication
I would reconstruct the likely path like this, while keeping the uncertain parts labeled as uncertain:
- A workflow or dependency path gained attacker control, likely through a compromised workflow context or a trusted release path.
- The workflow had permission to request an OIDC token.
- The token was exchanged for registry trust in a publish flow.
- The registry accepted publication because the claims matched the policy the maintainer had configured.
- The artifact ended up with provenance that matched the workflow, even though the workflow content or trigger was no longer trustworthy.
Only step 2 is the platform behavior. Steps 1, 3, 4, and 5 are the likely failure mode the report is pointing at, but I have not independently reproduced the incident.
The important lesson is that the attacker does not need to forge identity if they can run inside the identity boundary.
Why valid SLSA provenance did not automatically mean safe provenance
SLSA provenance is evidence about build origin. It answers questions like:
- what built this artifact?
- from which source revision?
- with which workflow or builder?
- under what reproducible build record?
That is useful, but it is not the same as release authorization.
A malicious or hijacked workflow can still produce a clean provenance record if the provenance accurately describes the compromised build. In other words, provenance can be honest and still be unsafe.
My take: this is the same mistake teams make with code-signing. They treat a valid signature as if it were a release approval. It is not.
The likely failure mode: trusted identity, untrusted change
The phrase I would use in a postmortem is:
- trusted identity
- untrusted change
The identity boundary was the workflow, branch, or environment. The change was either a malicious workflow edit, a compromised dependency, or another path that let attacker-controlled code run where the OIDC token was available.
If the publish policy trusted the repo too broadly, then the attack did not need to defeat cryptography. It only needed to become “the normal build.”
A safe lab model for testing the boundary
Minimal GitHub Actions workflow that requests OIDC
If you want to see how the boundary works without touching a real package, use a workflow that only requests and prints the token claims.
name: inspect-oidc
on:
workflow_dispatch:
permissions:
contents: read
id-token: write
jobs:
inspect:
runs-on: ubuntu-latest
steps:
- name: Request and decode the OIDC token
shell: bash
run: |
set -euo pipefail
response="$(curl -sS -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" "${ACTIONS_ID_TOKEN_REQUEST_URL}&audience=dev-audience")"
token="$(python3 - <<'PY' "$response"
print(json.loads(sys.argv[1])["value"])
PY
)"
python3 - <<'PY' "$token"
token = sys.argv[1]
payload = token.split(".")[1]
payload += "=" * (-len(payload) % 4)
claims = json.loads(base64.urlsafe_b64decode(payload))
keys = ["iss", "aud", "sub", "repository", "ref", "workflow", "job_workflow_ref", "environment"]
print(json.dumps({k: claims.get(k) for k in keys}, indent=2))
PYThe point of the lab is not to publish anything. The point is to make the claims visible.
Inspecting token claims and publish prerequisites
If the workflow can request a token, the output should show the claims that a registry or policy engine would evaluate. In a normal run, the fields that matter most are the workflow reference and the subject string.
Example of the kind of output you want to capture:
{
"iss": "https://token.actions.githubusercontent.com",
"aud": "dev-audience",
"sub": "repo:acme/widget:ref:refs/heads/main",
"repository": "acme/widget",
"ref": "refs/heads/main",
"workflow": "inspect-oidc.yml",
"job_workflow_ref": "acme/widget/.github/workflows/inspect-oidc.yml@refs/heads/main",
"environment": null
}
What to look for:
subshould match the exact repo and ref you intend to trustjob_workflow_refshould point to the specific workflow file you want to allowenvironmentshould be present only when you intentionally use environment-based release gatingaudshould match the consuming service, not some generic default
A quick repo audit also helps. I usually grep the workflow directory for release-related permissions:
grep -RIn --line-number 'id-token: write\|npm publish\|environment:\|workflow_call' .github/workflows
If that turns up more publishing jobs than you expected, the policy is already too broad.
What output to capture so the trust decision is visible
For a review, I would capture three things:
- the workflow file path
- the token claims
- the publish step or policy that accepted those claims
That makes the trust decision inspectable instead of implied.
A good reviewer should be able to answer:
- Which exact workflow was allowed to publish?
- Which branch or environment was trusted?
- Could a pull request or workflow edit reach the same token path?
- Did the policy require the exact
job_workflow_ref, or only the repository name?
If those answers are fuzzy, the boundary is fuzzy.
What the incident means for npm maintainers
Release automation should be narrower than repository access
My strongest opinion here is simple: repository write access is too broad to imply publish rights.
Maintainers should separate:
- code contribution
- CI execution
- release approval
- package publication
A developer can be allowed to merge a bug fix without being allowed to mint release credentials. A workflow can be allowed to build artifacts without being allowed to publish them. Those are different trust levels.
Provenance is evidence, not a substitute for policy
I would not ship a release policy that says, “the artifact has provenance, therefore it is safe.”
The better stance is:
- provenance tells you what happened
- policy tells you whether what happened was allowed
That is the core lesson from this incident as reported. Provenance helps you investigate and verify. It does not replace the decision about who may publish.
Dependency consumers still need anomaly detection and allowlisting
Consumers should not assume provenance solves upstream risk. It improves traceability, but it does not stop a maintainer account from being abused or a release workflow from being subverted.
Practical defenses on the consumer side:
- allowlist critical packages and monitor new releases
- alert on unusual version jumps or publish cadence
- prefer pinned dependencies for high-risk tools
- review provenance only as one signal, not the only signal
If a widely used package suddenly starts publishing more often, or from a workflow that changed yesterday, that is a signal worth investigating.
Defensive checks for platform and security teams
Pin the exact workflow, branch, and environment that may publish
Do not trust “any workflow in this repo.”
Prefer policies that bind publication to:
- one workflow file
- one branch or tag pattern
- one protected environment
- one publishing audience
If you let release jobs run from arbitrary branches, the trust boundary is already leaking.
Restrict OIDC subject and audience claims aggressively
The useful claims are the ones that let you pin the identity tightly. The risky policy is the one that accepts a broad subject like “any workflow from this repository.”
Better:
| Claim | Safer pattern | Weak pattern |
|---|---|---|
sub | Exact repo + exact branch or environment | Any ref in the repo |
job_workflow_ref | Exact release workflow file | Any workflow file |
aud | Registry-specific audience | Generic default audience |
environment | Protected release environment | Unset or optional |
That is the kind of policy that makes a malicious workflow edit harder to turn into a package publish.
Separate build, sign, and publish permissions
The cleanest design is to split the pipeline:
- build job: compiles and tests only
- sign job: creates attestations only
- publish job: runs in a protected environment with minimal permissions
If the same job builds, signs, and publishes, you have collapsed three trust decisions into one shell script.
Alert on workflow edits, token scope changes, and unusual publish volume
A lot of teams watch for source code changes and ignore workflow changes. That is a mistake.
Alert on:
- edits to
.github/workflows/*.yml - new
id-token: writepermissions - new or modified
npm publishsteps - changes to environment protection rules
- spikes in publish volume or package count
- provenance appearing from a workflow that never published before
If a package suddenly starts shipping from a different workflow path, that is worth paging someone.
What I would fix first
Reduce implicit trust in repo-wide GitHub Actions permissions
If I had to pick one fix, I would reduce the default trust around GitHub Actions permissions first.
The default should be:
- no OIDC unless the job absolutely needs it
- no publish permissions unless the job is the release job
- no release job without environment protection and explicit review
That alone removes a lot of accidental abuse paths.
Make publish rights explicit, narrow, and reviewable
The second fix is to make the publish path obvious enough that a reviewer can reason about it in one pass.
A maintainer should be able to answer, from the workflow diff alone, “what exactly can publish, from where, and under whose approval?”
If that answer takes a SOC analyst and three dashboards, the policy is not tight enough.
Conclusion: provenance helps, but it does not close the door
The practical lesson for npm and supply-chain defense
The ChainDrop report, as written, is a reminder that modern supply-chain security is now about identity boundaries, not just secrets.
GitHub Actions OIDC is useful. SLSA provenance is useful. npm trusted publishing is useful. But none of them replace a narrow release policy.
My position is straightforward: if a workflow can be turned into publish authority, then provenance alone is not enough. The control has to live in the workflow policy, the branch and environment restrictions, and the monitoring around changes to those rules.
Useful references:


