Security

npm SlugFest Explained: How Trusted Publishing Let Attackers Hijack 1,500 Packages

A naming flaw in npm Trusted Publishing exposed more than 1,500 packages to takeover with no stolen credentials. Here is how SlugFest worked and what to audit now.

Tyler Brennan

Runtime & Language Tooling Writer

Published 7 min read
Close-up of a woman coding using a laptop in an office environment, showcasing modern technology.
On this page 10 sections

SlugFest is a flaw in npm Trusted Publishing that let an attacker publish new versions of a package they did not own. The attacker only had to claim an abandoned GitHub repository name. No password or token theft was needed. Bloom Security counted more than 1,500 exposed packages before GitHub shipped a fix in October 2026.

This explainer is for maintainers who publish to npm from GitHub Actions, and for the engineers who depend on those packages. It covers how the SlugFest flaw worked, what GitHub changed, and the short audit worth running this week. Backtrace covers supply chain security for working developers, so the focus is on what you can check today.

What Trusted Publishing Is Supposed to Do

Trusted Publishing replaces long-lived npm tokens with short-lived credentials. A CI workflow proves its identity with an OpenID Connect (OIDC) token, which is a signed statement from GitHub about where a job is running. npm checks that statement and hands back a temporary publish credential.

The idea is sound. A stolen token is the classic way a package gets hijacked, and a token that expires in minutes is much harder to steal and reuse. Many maintainers moved to it after the string of worm attacks earlier this year.

A rusty, empty billboard frame stands against a vast clear blue sky, symbolizing abandonment.

The weak point was the binding. A maintainer tells npm which repository and workflow may publish a given package. If npm identifies that repository loosely, the whole trust model rests on a name.

How SlugFest Worked

According to Bloom Security's SlugFest research, npm tied a package to its GitHub repository by the owner and repo name, called the slug. GitHub also gives every repository a permanent numeric ID. npm only stored that ID after the first successful publish.

That gap created a window. Suppose a maintainer configured a binding, then renamed the repo, transferred it, or deleted the account before any version shipped from it. The old slug became free for anyone to register.

The attack then needed four steps.

  1. Find a package whose bound repository no longer exists under that name.
  2. Create a new repository at the freed slug.
  3. Add a workflow file with the same filename the binding expects, such as release.yml.
  4. Run it with the id-token write permission and receive a valid publish credential.

The OIDC token carried the reclaimed slug, so npm's exchange endpoint accepted it. The real maintainer's accounts were never touched. The attacker's build might even carry a valid Sigstore attestation, which means a tool that only checks for a signature could be fooled.

How Big Was the Exposure?

Bloom reported the problem to GitHub on June 6, 2026, and found more than 1,500 vulnerable packages. Those packages were downloaded over 10 million times in total, and more than 1 million times a month. The scope is narrower than the whole registry, since only packages with Trusted Publishing configured were at risk.

The reach grows when you add automation. Bloom estimates that an average endpoint installs about 70 agent-selected packages a month, and a developer machine about 350. Coding agents tend to grab the latest version without looking at where it came from. The Hacker News reported that the flaw stayed exploitable for roughly four months before the fix.

Hand holding a brass padlock, symbolizing security and protection

Backtrace editors checked the timeline against both sources. Bloom says its last reply from GitHub came on July 8, and that the fix landed on October 2 without a notice to the researchers. That is a long window for a registry that sits under most JavaScript projects.

What Did GitHub Change?

GitHub's changelog entry from October 2 describes the fix. It does not switch npm to numeric IDs. It shortens the window instead.

Here is what changed, in plain terms:

What Did GitHub Change?
ChangeWhat it means
48-hour expiryA binding with no successful publish stops working after 48 hours.
ValidationThe first successful publish marks a binding as validated and exempt from expiry.
Identity changesChanging the repository or project starts a new trust relationship and a fresh 48 hours.
issue_comment eventsnpm now rejects Trusted Publishing tokens from issue_comment triggers.
Existing restrictionThe earlier block on pull_request_target tokens still applies.

The result is a smaller attack window. An abandoned binding can no longer sit dormant for months waiting for someone to claim the slug. Bloom notes that PyPI already resolves the numeric repository ID at the time a binding is created, which closes the hole at the source.

Is My Package Still at Risk After the Fix?

Possibly, in a narrow case. The fix expires unvalidated bindings, so a binding that never published anything will stop working after 48 hours. A validated binding that points at a slug you later abandon is a different story. Treat renames, transfers and deletions as events that require cleaning up the binding.

Software developer analyzing code on a tablet in a modern office workspace.

A Short Audit for Maintainers

Start with the list of bindings. Bloom recommends running npm trust list for each package you maintain and confirming that every slug still points to a repository you control. It takes a few minutes per package.

Then work through these steps in order:

  1. Delete any binding whose repository was renamed or transferred, and recreate it against the current slug.
  2. Remove the binding before you delete a repository you will not bring back.
  3. Publish a version right after creating a new binding, so it becomes validated.
  4. Search your workflows for issue_comment triggers and move publishing to push, release or workflow_dispatch.
  5. Watch for publish events you did not start.

Do not skip packages you consider finished. Dormant packages are the likeliest to have a stale binding, because nobody has published or renamed anything in a while. A small utility with ten thousand weekly downloads and no recent commits is exactly the profile an attacker would look for.

The last item matters most for packages with many dependents. A quick alert on unexpected releases buys you hours, and hours are what limit the damage in a supply chain incident.

Why the issue_comment Change Matters

The second half of the fix is easy to overlook. GitHub Actions can run a workflow when someone comments on an issue. If that workflow also holds publish rights, a comment from a stranger can become a trigger for a release. npm already blocked tokens from pull_request_target events. It now blocks issue_comment events too.

Check how your release workflow starts. Publishing from a push to a protected branch, a tagged release or a manual dispatch is easier to reason about. Each of those needs someone with write access to act first. A comment does not.

One more detail is worth knowing. Expired bindings stay visible in the trusted publisher settings, but they do not count against the per-package limit. You can recreate one at any time, which starts a new 48-hour window. Plan to publish within that window, or the binding lapses again.

What Dependents and Security Leads Should Do

If you only consume packages, you cannot fix the binding. You can slow the spread. A short delay before adopting brand-new versions gives scanners time to flag a bad release. Many teams now set a minimum release age in their package manager for exactly this reason.

Bloom also advises auditing everything agents can install, including extensions, MCP servers and plugins. Its earlier Extension Resurrection research found 87 packs on Open VSX and 677 on the VS Code Marketplace that pointed at extensions that did not exist yet. The pattern is the same as SlugFest: a name that nobody owns.

Closeup of switch in server with connectors and adapters connected to plastic device in dark room on blurred background inside

Our guide to how software supply chain attacks work covers the broader playbook. Agent-driven installs add their own risk, which we cover in the piece on hallucinated dependencies from AI coding tools. If a package in your tree was already hit, the steps in our Shai-Hulud response checklist still apply.

The Bigger Lesson About Names

SlugFest is not really about npm. It is about trusting a name that someone else can later own. Package names, repository slugs, extension names and even internal service names all share the problem. Any time identity rests on a label, ask what happens when the label is freed.

Organizations can apply the same test to their own internal tooling. Look at deploy keys, cloud trust policies and CI roles that name a repository. A policy that trusts "any workflow in org/repo" is only as safe as that repository name staying yours. When a team renames a repo or retires a project, the cleanup list should include every trust relationship that mentioned it.

Numeric IDs, signed provenance and short expiry windows all help. None of them replace the habit of cleaning up after a rename. A binding is a permission, and permissions should not outlive the thing they were granted for.

Conclusion

SlugFest showed that a convenient shortcut in npm Trusted Publishing could turn an old repository name into a publish key. GitHub's 48-hour expiry closes the worst of it, but the cleanup is still yours. Run npm trust list today, delete stale bindings, and publish once after creating each new one. Then set up an alert for releases you did not start.

What is the npm SlugFest vulnerability?
SlugFest is a flaw in npm Trusted Publishing found by Bloom Security. npm tied a package to a GitHub repository by its name, not its permanent numeric ID. An attacker who claimed an abandoned repository name could publish new versions of the package without any stolen credentials.
How do I check my npm Trusted Publishing bindings?
Run npm trust list for each package you maintain and confirm every listed repository slug still points to a repository you control. If a repo was renamed, transferred or deleted, delete the old binding and recreate it against the current slug, then publish once to validate it.
Did GitHub fix the SlugFest flaw?
GitHub shipped a mitigation on October 2, 2026. Unvalidated Trusted Publishing configurations now expire 48 hours after creation, and a binding becomes permanent only after its first successful publish. npm also rejects tokens issued from issue_comment events in GitHub Actions.
Can AI coding agents make a package hijack worse?
Yes. Coding agents often install the latest version of a package without checking where it came from, so a malicious release can spread quickly. Bloom estimates a developer machine installs about 350 agent-selected packages a month, which is why a release-age delay is worth setting.

Written by

Tyler Brennan

Runtime & Language Tooling Writer

11+ years experience

Tyler spent nine years as a full-stack engineer at a mid-sized e-commerce company, where he owned the Node.js services and the build pipeline behind them. He ran four major runtime upgrades there and still keeps the habit of reading changelogs for what gets removed before what gets added. At Backtrace he covers language releases, package managers and the local tooling that decides how fast a team moves. His rule is simple: an upgrade guide is only useful if it lists the failure you will see first and the command that finds it.

Credentials

  • B.S., Computer Science, University of Wisconsin-Madison
  • Former Senior Full-Stack Engineer, e-commerce platform (2014-2023)
  • OpenJS Foundation Node.js Application Developer certification