X-Ops

GitHub Hardens npm and Actions by Default: What Changes in Your Pipeline

# GitHub Hardens npm and Actions by Default: What Changes in Your Pipeline

For the past three years, supply-chain attacks against the JavaScript ecosystem have followed a depressingly predictable script. A maintainer with publish rights on a high-traffic package gets phished. Their credentials, including a one-time 2FA code, are forwarded in real time to an attacker. Within minutes, a poisoned version of `chalk`, `debug`, or `nx` lands on npm and is consumed by billions of weekly downloads before the maintainer even notices the password reset email. GitHub's response, announced in late August 2026 and detailed in a series of posts by principal product security engineer Greg Ose and principal software engineer Zachary Steindler, is to remove the assumptions that made that script work — not by patching one vulnerability, but by changing the defaults of the two surfaces that mattered most: npm itself and GitHub Actions.

This article walks through every default that changed, explains why each one matters, and gives you a concrete checklist of what to verify in your own pipelines this week.

The npm read-only protection

The single most consequential change is invisible until you trigger it. Any npm account that GitHub classifies as "high-impact" — meaning the account publishes packages that collectively receive more than a million downloads per week, or maintains packages that are direct dependencies of the top one thousand most-downloaded packages on the registry — is now placed into read-only mode for seventy-two hours after two specific events: changing the primary email address on the account, or using a 2FA recovery code.

The reasoning is straightforward. The chalk/debug attack of September 2025 began with a phishing email impersonating npm support. The maintainer, Josh Junon, entered his credentials and a fresh TOTP code into a fraudulent portal at `npmjs[.]help`. The attackers captured both in real time and published malicious versions of at least eighteen packages within sixteen minutes. None of the post-mortems identified a way to make TOTP itself resistant to this kind of adversary-in-the-middle phishing — the protocol was working exactly as designed, and the attacker simply proxied the challenge in real time. The defense has to live elsewhere.

Read-only mode is GitHub's answer. During the seventy-two hour window, the account cannot publish new versions, cannot add new maintainers, and cannot rotate publish tokens. The maintainer can still log in, still browse their package list, and still yank malicious versions — but they cannot accidentally publish again while the threat is still active. If the attacker did manage to publish during the takeover, the yank path is the only thing that limits the blast radius. The seventy-two hour cooldown makes that window long enough to actually use.

For platform engineers running internal mirrors or private registries, this change has a knock-on effect: if your organization has a single npm account used to publish shared internal libraries to a private registry, that account is almost certainly "high-impact" by the definition above. You should expect the read-only behavior to fire the next time someone rotates the password or recovers from a lost authenticator. Bake that cooldown into your release process. If your team treats npm publish as a hot-button action that has to happen within minutes of a code merge, you are about to have a very bad Tuesday.

The actions/checkout default change

The second default change is in the action that almost every workflow runs first: `actions/checkout`. For years, the action checked out the contents of the triggering ref by default, including pull requests from forks. Combined with `pull_request_target` — a trigger that runs with write permissions and access to secrets — this combination became the standard recipe for a class of attack where an external contributor submits a pull request, the workflow checks out the PR head, and the PR head happens to contain a `.github/workflows/exfiltrate.yml` or a `package.json` with a malicious `preinstall` script.

The new default for `actions/checkout` is that workflows no longer check out untrusted fork code under the commonly exploited triggers (`pull_request_target` from forks, `workflow_run` from forks) unless the team explicitly opts out by setting `persist-credentials: false` and re-enabling the checkout manually. The change was backported to the older 4.x line of the action, so it applies to pipelines that have pinned an earlier release.

The practical effect is that the long tail of GitHub Actions pipelines that have not been audited in the past year — and there are millions of them — silently became safer on the day the default changed. Auditors who go looking for the classic `pull_request_target` + checkout-of-`github.event.pull_request.head.sha` pattern will find that the action now refuses to perform the dangerous checkout unless the workflow author has explicitly carved out an exception.

For teams that genuinely need to run untrusted fork code in a privileged context — for example, to build a preview environment for an external contribution — the right pattern has not changed: run the untrusted code in a sandboxed job with no secrets, no write permissions, and a network egress allowlist. The new default just removes the foot-gun for teams that did not know they were holding one.

Workflow execution policies

A new governance primitive, workflow execution policies, gives organization admins a way to constrain which workflows can run, who can trigger them, and which trigger types are permitted. The policy is defined at the organization level and evaluated before the workflow starts. A policy can require, for example, that any workflow triggered by a `pull_request_target` event must also pin the action versions to a SHA and must not reference any third-party action that has not been allowlisted.

The model is deliberately conservative. If a workflow violates the policy, it does not run at all — the action fails with a clear error message naming the policy that was violated, not a silent success or a cryptic YAML parse error. This is a meaningful difference from the previous best-effort guidance, which was a wiki page.

For platform teams rolling this out, the migration order matters. Start with a policy that allows everything (an audit-only mode) so you can see which workflows would have been blocked. Then enable blocking in non-production organizations. Then enable it in production with an exemption list for known edge cases. Skipping straight to blocking in production is a recipe for an outage that traces back to a workflow nobody remembers owning.

The cache poisoning path is closed

The Actions cache has been a quiet source of supply-chain risk for years. A workflow that uses `actions/cache` to persist `node_modules` or build artifacts between runs implicitly trusts whatever is in that cache. If an attacker can poison the cache — for example, by triggering a workflow with a ref that writes a malicious binary to a path the next run reads — they can get their code to execute in a context that trusts the cache key.

The new default makes the Actions cache read-only for untrusted triggers. Fork pull requests and external contributors can no longer write to the cache, only read from it. The cache poisoning path that has been discussed in private security channels since at least 2023 is now closed by default.

The operational impact is small for most teams. The cache continues to work as a read-through cache for forks, and internal contributors can still populate it. The only workflows that will notice are ones that explicitly relied on a fork contributing to the cache, which is an unusual pattern and almost always a mistake.

Trusted publishing and CircleCI

Trusted publishing is the part of GitHub's strategy that does not just change defaults but removes entire classes of credentials from the pipeline. Instead of storing a long-lived `NPM_TOKEN` in repository secrets, trusted publishing uses OIDC to issue a short-lived token at the moment of publish, scoped to the specific workflow that requested it and the specific package being published. The token never exists outside the publish step and cannot be exfiltrated by a separate compromise of the repository's secret store.

Until August 2026, trusted publishing on npm was supported for GitHub Actions, GitLab CI, and a handful of smaller providers. The new release adds CircleCI, closing the gap for teams that standardized on CircleCI before adopting GitHub Actions and have been unable to remove their `NPM_TOKEN` secrets because of that. The migration is straightforward: in the npm package settings, add CircleCI as a trusted publisher, configure the corresponding context and project ID in CircleCI's OIDC settings, and remove the `NPM_TOKEN` secret from the CircleCI context.

The most useful comment on the original InfoQ thread, from a maintainer using the handle pimterry, captures the security logic succinctly: "Trusted staged publishing helps a lot: you have to independently pwn the workflow _and_ then complete a separate 2FA flow as a maintainer. The workflow never sees any keys that can publish independently." That is the property that matters. A stolen workflow identity alone is not enough. The attacker still has to convince a human to complete a second factor at publish time, which is what staged publishing enforces.

The Actions network firewall

In technical preview as of the announcement is an Actions network firewall that logs outbound traffic from workflow runs. The firewall does not block by default — that would break too many existing workflows that pull images from public registries or call out to deployment APIs. What it does is produce an auditable record of every outbound destination a workflow reaches, indexed by workflow, job, and step. The use case is detection, not prevention: a workflow that suddenly starts calling an unfamiliar IP range or resolving an unfamiliar domain is a strong signal that something is wrong.

For security teams that already operate a SIEM, the firewall output can be forwarded to a custom log destination. For teams that do not, the GitHub UI surfaces the most relevant outliers directly on the workflow run page. The preview is free during the preview period and will be priced per-workflow-run once it goes general availability, though GitHub has not yet announced the GA pricing.

What platform engineers should do this week

The changes are defaults, which means they apply to new workflows and to workflows that have been touched recently. Long-dormant workflows on repositories that have not seen a push in months may still be on the old behavior. A short audit checklist:

1. Find every workflow in your organization that uses `pull_request_target` and verify that it does not check out fork code with the default `actions/checkout` settings. If it does, either explicitly opt out or move the untrusted work into a sandboxed job.

2. Find every npm publish step in your organization and verify that the secret in use is a short-lived trusted publishing token, not a long-lived `NPM_TOKEN`. If you find a long-lived token, rotate it once you have migrated.

3. Enable workflow execution policies in audit-only mode for at least one organization and review the violations report. The output is the most efficient way to find the long tail of workflows that have drifted out of compliance.

4. If you operate an internal registry that mirrors npm, verify that your mirror's service account is not classified as high-impact. If it is, make sure the team understands the seventy-two hour cooldown.

The supply-chain attack surface of the JavaScript ecosystem did not shrink because of a single patch. It shrank because GitHub changed the assumptions that several years of attacks had been quietly relying on. Defaults matter more than any single opt-in control, because defaults reach the workflows that nobody has time to audit.