// hackerlogs
login+ register
Supply ChainAgentic AIThreat BriefHigh

npm Stored the GitHub Slug. The Id Waited for a Publish.

Bloom's 13 October post says npm matched a GitHub slug until the first publish. The 2 October changelog expires unvalidated configs after 48 hours.

The short answer

The primary is Golan Myers's SlugFest post at Bloom Security, 13 October 2026. npm trusted publishing stored a GitHub slug and matched it until the first publish. Bloom published from a second organization onto a package it controlled. The 2 October changelog expires unvalidated configurations after 48 hours. Bloom did not publish the package list.

Key takeaways

  • The primary is Golan Myers at Bloom Security, 13 October. A trusted publisher stored the GitHub owner/repo slug. The permanent repository id was recorded at the first publish, not when the binding was created.
  • Bloom published from a second organization onto a package and repositories it controlled. The shared fact was the freed slug. No maintainer credential was used in that test.
  • The GitHub changelog on 2 October expires unvalidated configurations 48 hours after creation. A first successful publish validates the binding. GitHub's statement does not say older publishes were rewritten.
  • Bloom's scan says more than 1,500 packages and more than 10 million downloads. They withheld the names. The scan section still calls the issue prevalent. Do not treat 1,500 as tonight's open set.
  • A provenance attestation from this path can be valid and still name the other repository. Checking that a signature exists is not the same check as reading which repository signed.
  • From the 6 June report to the 2 October changelog is 118 days. Bloom's numbers box says over 120. Use the dates in the post.

Golan Myers's SlugFest post at Bloom Security, 13 October 2026, says npm trusted publishing stored a GitHub owner/repo slug and matched that string until the first publish from the repository. Bloom then published a version of a package it owns from a second organization's repository. The slug was the thing the two repositories shared. No maintainer credential was required for that test.

Hacker News tonight led with an OpenAI conference-room arrest, the Gemini 4 Argon launch, and a Ledger hardware implant. US X trends at 22:36 UTC were football, a film, and that launch. Reuters in this window had no new operator control failure. The registry fact is Bloom's post. The changelog it points at is from 2 October.

High High because a repository that did not originally own the binding obtained a publish credential for a package name, in a test Bloom ran on infrastructure it controlled. It is not a count of malicious versions on npm. Myers told Help Net Security that wild exploitation is unknown.

Three panels for npm trusted publishing: a slug match until the first publish, a 48-hour expiry on unvalidated configs, and bindings a maintainer still has to recheck.

What the binding stored.#

The npm trust docs for npm trust github take --repository as owner/repo, --file as the workflow filename, and an optional environment. They do not take a repository id. npm trust gitlab takes a project path of the form group/project. npm trust circleci takes organization, project, and pipeline definition UUIDs. The GitHub and GitLab forms are names. The CircleCI form is already ids.

GitHub's OpenID Connect tokens carry repository, repository_id, and repository_owner_id. The numeric claims survive a rename or a transfer. Myers says npm ignored them until the first successful publish from that repository. Until then, the exchange matched the slug and the workflow filename. This brief does not repeat the steps Bloom used to show that.

PyPI's Warehouse internals say that, for GitHub, PyPI looks up the owner's numeric id when the trusted publisher is configured and checks repository_owner_id when it mints a token. That is an owner-id check at configuration time. It is the contrast on the page this brief actually read. Bloom says Warehouse stores the repository id as well. This read did not go past the owner-id section.

The package clock and the impact clock.#

The package clock is 2 October. Unvalidated trusted publishing configurations expire 48 hours after they are created. The same changelog says npm rejects trusted publishing tokens from GitHub Actions issue_comment events, next to the existing pull_request_target restriction. That is a separate control on which workflow events may publish. It is not the slug check. Bloom dates the fix "2.10" and links this changelog. Read 2.10 as 2 October, the page they linked, not as an npm release number.

The disclosure clock is the post's own timeline. Myers reported it through GitHub's HackerOne program on 6 June 2026. It was triaged and validated on 9 June. The last reply he cites is 8 July: the team was working on a fix and determining risk and severity. Six later enquiries, including a mediation request, went unanswered. Before publishing, Bloom sent the draft and ran the test again, which is how they found the fix. The team then apologized for the silence and said the fix was in place. The public post is 13 October. From 6 June to 2 October is 118 days. The numbers box in the same post says over 120 days. Count the dates.

The impact clock is Bloom's scan, and the post does not date it against the changelog. More than 1,500 packages, more than 10 million downloads in total, more than a million a month. Myers says that figure is among packages with trusted publishing configured, not the whole registry. He estimates adoption at under 20 percent of active packages. The scan section says they are not releasing names because the issue is still prevalent. The fix section says the 48-hour rule is already in place. Hold both sentences. A list withheld on 13 October is not a list you can diff, and it is not evidence those 1,500 bindings are still unpublished.

Myers told Help Net Security it is unknown whether anyone exploited the flaw in the wild. The post's customer sentence is different: a few weeks ago Bloom blocked an agent that was trying to install one of the packages on the vulnerable list. An install of a package they had classed as exposed is not a malicious tarball.

The agent rates are Bloom's telemetry across dozens of organizations, not an npm census. Each agent brought in 0.3 to 1.2 new packages a day, and developer machines 2 to 3. An average endpoint ran about three agents, a developer machine five or more. That is about 70 direct agent-installed packages a month on a typical endpoint, and close to 350 on a developer machine, not counting dependencies. The title's "millions of AI agents" is the population Bloom says it studied. It is not a count of compromised installs.

GitHub's transfer docs retire an owner/name pair when the repository had more than 100 clones or more than 100 Actions uses in the week before the transfer, or when it contains a Marketplace action. Myers told Help Net that Bloom's set was the slugs that stayed claimable. Retirement on transfer is not the registry's check, and this brief did not re-read the rename and delete rules.

Bloom says the same slug gap was in npm's GitLab trusted publishing. The changelog's expiry sentence is about unvalidated configurations, not only GitHub. It does not say existing GitLab bindings were rewritten. The issue_comment rejection is a GitHub Actions event.

  1. Bloom reports through HackerOne. Validated 9 June. Last reply Myers cites is 8 July.
  2. Changelog: unvalidated configs expire 48 hours after creation. issue_comment tokens rejected. 118 days after the report.
  3. Public post. Scan says 1,500+ and withholds names. Fix section says the 48-hour rule is in place.

A valid attestation can name the other repository.#

Myers says a publish through the reclaimed slug still produces a Sigstore provenance attestation, and the attestation names the repository that ran the workflow. Downstream tooling that only checks whether a package is signed will call that attestation valid. The check that would have mattered is whether the repository in the attestation is the repository the maintainer still intends.

That is a different failure from a stolen publish token that runs inside the real repository. Here, if Bloom's account holds, the attestation is honest about a repository the package did not mean to trust. Reading "provenance: present" does not answer which repository.

What to do with the binding.#

Maintainers should run npm trust list for each package. The CLI page says that command needs npm 11.15.0 or newer. The website docs say a package can hold up to 10 trusted publishers, and that an existing connection cannot be edited in place: delete it and create a new one. The CLI page says the registry currently supports one configuration per package. Those two official pages disagree. Read the list you actually have. If a GitHub or GitLab slug does not resolve to a repository you control, remove that binding and create one that does. Do not assume the 48-hour expiry rewrote a binding that had already published.

If you create a binding, publish from it inside two days. The docs say an unvalidated configuration expires and can no longer be used or edited. Recreate it. The same page says npm does not verify the configuration when you save it. A wrong filename or a wrong repository shows up when a publish is attempted, not when the form is saved.

For a package you install, there is no Bloom list to diff. Pin the version you reviewed. If you do check provenance, read the repository on the attestation. A new version that arrived through trusted publishing is still a new version. Bloom's blocked agent install is their telemetry, not a hash you can match.

Review GitLab project paths the same way. The changelog does not give them a separate sentence, and the CLI still asks for the path.

Frequently asked

Are the 1,500 packages still open tonight?

Bloom does not say that. The scan section withholds names because they call the issue prevalent. The fix section says a binding with no publish inside 48 hours is invalidated, and dates that behavior to 2 October. The post never dates the scan against the changelog. Do not promote 1,500 into a live count, and do not retire it either.

Does a provenance attestation catch a reclaimed slug?

Bloom says the publish still gets a signed attestation, and the attestation names the repository that ran the workflow. A check that only asks whether an attestation exists will pass. The useful check is whether that repository is the one the maintainer still controls.

Did the 2 October changelog cover GitLab too?

The expiry sentence is about unvalidated trusted publishing configurations. It is not limited to GitHub Actions. The rejection of issue_comment tokens is. Bloom says the slug gap was also in the GitLab implementation. The GitLab CLI flags are still a project path, not a numeric id. The changelog does not say existing GitLab bindings were rewritten.

Was this exploited on the public registry?

Myers told Help Net Security it is unknown. Bloom's in-house test published a package it already owned. The customer event in the post is an agent trying to install a package Bloom had classed as vulnerable, which Bloom says it blocked. That is not a malicious version you can point at.

Sources

Related