GitHub's Disabled Flag Was Never Part of Your Pipeline
Pinning Actions shields your CI from the platform's mistakes, but this worm class also spreads through publish tokens, install scripts and mitigations that stand in for patches.
A workflow step written as uses: owner/action@v3 resolves when the job runs, not when someone reviewed it. Whoever controls that repository can move the tag. The platform can also switch the Action between disabled and enabled, and that switch is the one that failed here. The only reference nothing upstream can change is a full 40-character commit SHA of a version your team reviewed. GitHub's org-level allowed-actions policy can enforce pinning, so nobody merges an unpinned step.
Pinning closes one door of several
The worm class reaches beyond Actions. Risky Business notes that the original Shai-Hulud spread by harvesting publish credentials and republishing victims' packages. A CI job that holds a long-lived NPM_TOKEN and runs install scripts is the real prize, however its Actions are pinned. Attackers keep refining that entry point. North Korea's PolinRider has spent months A/B testing its npm malware to find the most effective versions. That is a growth experiment aimed at your install step.
| Control | What it closes | Cost |
|---|---|---|
| SHA pin plus allowed-actions policy | A retagged or re-enabled Action running in your CI | Updates need a review-and-bump routine |
Read-only default GITHUB_TOKEN, write granted per job | A compromised step pushing code or releases | Jobs that write need explicit grants |
| npm trusted publishing (OIDC) | Stolen long-lived publish tokens | Migrating each package's publish setup |
Install scripts off by default (already the default in pnpm 10; allowlist via onlyBuiltDependencies) | Code executing at install time | Packages with native builds need allowlisting |
Release cooldown (minimumReleaseAge in pnpm or Renovate, or Dependabot's cooldown) | Freshly poisoned versions | Security fixes wait too |
The cooldown's cost is the one that bites. A 3–7 day cooldown delays security fixes as well as poisoned releases. Build an explicit override tied to published advisories, or engineers will learn to switch the cooldown off entirely. Pins carry a similar cost. Someone has to review and bump them, or they quietly age into a different risk.
Compensating controls decay the same way
The PeopleSoft story shows the same failure with a control teams owned themselves. ShinyHunters, who hit Oracle PeopleSoft with a zero-day in June, found a route around the firewall rules some companies had deployed in place of Oracle's patch, and the data theft resumed. A firewall rule blocks one exploit shape. The vulnerable code behind it stays exactly as vulnerable.
Treat every firewall rule, WAF signature or feature flag standing in for a patch as a temporary bridge with an expiry date. Each one needs an owner, a ticket and a hard deadline, because nothing else will retire it.
A dependency reference that can change without your review hands someone else the decision about when a worm is dead.
The same logic runs through the rest of today's edition. Disabling an Action, deleting an agent and rolling back a deploy each flip a switch while the dangerous thing survives somewhere else. The fixes that last remove that thing directly: an immutable pin, a deleted token, an applied patch.
What to do
Pin every third-party Action to a full 40-character commit SHA of a reviewed version this week. Enforce it with the org-level allowed-actions policy, and set default GITHUB_TOKEN permissions to read-only in the same change.
Move npm publishing to trusted publishing (OIDC) this sprint and delete long-lived NPM_TOKEN secrets from CI. Then turn off install scripts by default and add a release cooldown with an override tied to advisories.
Inventory every firewall rule, WAF signature or feature flag standing in for a patch this sprint, starting with PeopleSoft. Give each one an owner, a ticket and a hard expiry date.