SlugFest: npm trusted publishing trusted a reusable name
npm Trusted Publishing let anyone who claimed a freed GitHub repo name publish to the bound package. What GitHub changed and what to check.
By Yaali. October 15, 2026, 6 min read, Supply chain, Vulnerabilities, Threat intel.
Bloom Security has disclosed SlugFest, a flaw in npm Trusted Publishing that let an attacker publish new versions of someone else's package without stealing a token. npm checked the trusted repository by its owner/repo name, the slug, rather than by GitHub's permanent repository ID. If the bound repository or its owner account was renamed, transferred or deleted, whoever registered the freed name could publish. Bloom counted more than 1,500 exposed packages with over 10 million downloads in total and more than a million a month. npm's GitLab Trusted Publishing integration had the same gap.
Bloom reported the flaw through GitHub's HackerOne program on June 6, GitHub validated it on June 9, and the fix appeared in a GitHub changelog on October 2, almost four months later. Bloom says it has not seen the flaw used in the wild, but it reproduced the full attack and is not naming the affected packages because, it says, the issue is still prevalent. If you maintain npm packages that publish from GitHub Actions or GitLab CI, check your trust bindings this week. If you only install packages, the lesson is about what provenance does and does not prove.

How it works
Trusted Publishing, generally available on npm since July 2025, replaces long-lived npm automation tokens with OpenID Connect (OIDC). The CI job asks GitHub for a signed identity token describing the repository and workflow that is running, and npm swaps that token for a publish credential that expires within minutes. A maintainer sets it up with npm trust github <package> --repo owner/repo --file release.yml --allow-publish, which stores three things: the slug, the workflow filename and an optional environment name.
GitHub's token also carries repository_id and repository_owner_id, numbers that never change through a rename or transfer. npm ignored them until the binding's first successful publish. Before that, the token exchange succeeded whenever the token's repository claim matched the stored slug and the workflow filename matched. Golan Myers of Bloom put it this way: the system "checks the address on the door rather than who owns the house."
That left a gap whenever a binding existed but had not yet published and its slug became free. The attack Bloom describes takes four steps:
- Create a repository at the freed slug. GitHub gives it a new repository ID, which npm did not check.
- Add a workflow with the bound filename, for example
release.yml, and theid-token: writepermission. - Run it to mint an OIDC token that names the reclaimed slug.
- Hand the token to npm's exchange endpoint, receive a short-lived publish credential and push a new version.
No insider access, credential theft or race condition is needed. Bloom ran the attack end to end against an npm scope it owns, publishing from a repository in a different organisation where the slug was the only link. The rogue release carried a valid Sigstore provenance attestation naming the attacker's workflow run, so a tool that only checks whether an attestation exists would accept it.
GitHub permanently retires some popular owner/repo names when an account is renamed or deleted; Bloom counted only slugs that could still be claimed.
What attackers are doing
Nothing confirmed. Bloom has seen no exploitation in the wild, and Myers told Help Net Security it is unknown whether anyone used the flaw. Bloom did report one related event: its product blocked a customer's AI coding agent from installing one of the affected packages.
The AI agent point is why Bloom thinks the impact would have been large. From telemetry across dozens of organisations, it estimates that agents on a typical endpoint install about 70 packages a month, and close to 350 on developer machines, not counting transitive dependencies. Agents usually take the latest version and rarely check where it came from, so a malicious release with a postinstall script, which npm runs automatically on install, would execute without anyone reading the changelog.
What GitHub changed
The October 2 changelog makes unvalidated trusted publishing configurations expire 48 hours after creation; after that they can no longer authorise a publish. A configuration becomes validated, and exempt from expiry, after its first successful publish, which is the point where npm binds it to the repository's numeric identity. Changing the repository or project in a configuration starts a new trust relationship with a fresh 48-hour window, while ordinary edits do not reset the clock.
The same update makes npm reject trusted publishing tokens minted by GitHub Actions issue_comment events, alongside the existing pull_request_target block. That trigger was the route in the August 28 compromise of @7nohe/openapi-react-query-codegen, where a comment on a pull request got the project's own workflow to publish ten poisoned versions with valid provenance.
One question remains open: the changelog does not say what happened to unvalidated bindings created before October 2. Bloom says GitHub's last substantive reply came on July 8 and six follow-ups, including a mediation request to HackerOne, went unanswered; npm confirmed the fix, and apologised for the slow communication, after Bloom sent it the findings ahead of publication on October 13.
What to do

If you maintain packages
- Run
npm trust list <package>for every package you publish (npm 11.15.0 or later, with two-factor authentication on your account). Confirm each repository it shows still exists under that exactowner/repoand belongs to you. - For any binding pointing at a renamed or transferred repository, run
npm trust revoke <package> --id=<trust-id>with the ID from the list, then recreate it withnpm trust githubagainst the current slug. npm allows one configuration per package, so revoke comes first. - Publish straight after creating a binding, so it is validated well inside the 48-hour window.
- Treat deleting, renaming or transferring a bound repository as a revocation event, and remove the binding first. The same applies to renaming a GitHub organisation or user account.
- If a publish workflow listens to
issue_comment, move it topush,releaseorworkflow_dispatch, which the changelog lists as allowed. - Check your package's version history on npmjs.com for releases you did not make. The provenance section on each version links the source repository and workflow run, and any run outside your repository is a red flag.
If you consume packages
- Commit lockfiles and install with
npm ci, so builds do not pick up new versions on their own. - Add a delay before new versions are used:
min-release-agein npm 11.10.0 and later (in days), orminimumReleaseAgein pnpm (in minutes, one day by default from pnpm 11). - When you review an upgrade, open the provenance on the version page and check that the repository it names is the one the project really lives in. A signature from the wrong repository is still a valid signature.
- In CI jobs that do not need lifecycle scripts, set
ignore-scripts=truein.npmrc, and apply the same rules to installs made by AI coding agents.
The wider lesson
A trust binding is only as stable as the identifier it stores. Repository and account names can change hands, so any integration you run that trusts a name, such as a cloud OIDC role trust policy or a deployment allowlist, deserves the same check. PyPI's Warehouse records the immutable repository and owner IDs when the trust is created, which is the fix Bloom proposed for npm.
Our red team code review traces who can publish from your repositories and CI, and our platform and cloud engineering team can set up trusted publishing and OIDC trust policies tied to fixed identities. Open the chat and Yaali, our AI agent, will pass your question to an engineer.
Sources: Bloom Security research, GitHub changelog, npm trust command docs, npm config docs, Help Net Security, The Hacker News, The Next Web, Bromure, GitHub docs on account renames, Socket on @7nohe/openapi-react-query-codegen, StepSecurity, Classmethod on min-release-age.
Read next
- LuaRocks.org breach: bytecode escaped the rockspec sandbox
- Plex2Shell: public exploit turns old Plex servers into miners
- SiteGuard WAF flaw: save the evidence before you patch
Back to the blog, or tell us about your system in the chat. Yaali, our AI agent, answers first and brings in an engineer.