Arrange Act Assert

Jag Reehals thinking on things, mostly product development

The GitHub and npm settings open source maintainers should turn on before they need them

08 Jun 2026

In the middle of a supply-chain incident, the maintainer is not just fixing packages. They are locked out of their account, answering reports, trying to contact registries, trying to warn users, and trying to prove what happened.

That is the part we do not talk about enough.

Most open source maintainers are not companies. They do not have incident response teams. They do not have a security department. They have a GitHub account, an npm account, a laptop, and a lot of people depending on them.

Security advice often assumes the maintainer is the weak link. That is backwards. The maintainer is the last line of defence, usually unpaid, usually alone, and often locked out of the systems they need during the incident.

This is the checklist I wish every maintainer had before something goes wrong. No single setting saves you, so it works in layers: the account, the branch, the release path, the workflow, the tokens, the files, the tripwires, and the recovery plan.

1. Protect your GitHub account

Everything below assumes the attacker does not control your GitHub account. Start here, because once someone has push access, a lot of "secure CI" assumptions collapse.

Use phishing-resistant login where you can. GitHub supports passkeys and hardware security keys, and a passkey signs you in and satisfies 2FA in one step. An OTP app is better than SMS; a passkey or security key is better than both, because there is no code to phish.

The ten-minute audit:

Calendar this twice a year. Credentials accumulate; nobody deletes them without an appointment.

2. Protect your release branch

Every package repo should protect main. In Settings → Branches (or the newer rulesets):

For solo maintainers there is an obvious objection: who reviews my PR? Be honest about what this layer does. Branch protection will not save you from yourself, and it will not save you from a fully compromised account that can change the rules. But it stops accidental pushes, basic automation abuse, and compromise of a lower-privilege collaborator. It also creates friction, and sometimes friction is the control: an attacker scripting hundreds of repos goes around protected branches rather than through them. Section 3 closes the OIDC snapshot-branch path when they go around.

The same June 2026 campaign also landed direct commits on main with stolen credentials, disguised as ordinary feature work. Branch protection with required review and signed commits makes that path much harder. The trusted publishing post covers the OIDC half.

3. Protect your release environment

This is the layer that stopped mattering being theoretical in June 2026, when a worm published malicious versions through projects' own trusted publishing pipelines by pushing throwaway snapshot-* branches containing a copy of the release workflow. The publishes carried valid provenance. See the mechanics and the fix for details; the short version:

Jobs that reference an environment must satisfy its protection rules before they run. A workflow on a throwaway branch does not get the environment, does not get the OIDC claim, and the publish is rejected.

The principle:

A workflow should not be allowed to publish just because it exists. It should have to come from the release path.

4. Lock down GitHub Actions permissions

Default workflow permissions should be boring. Release permissions should exist only on the release job.

# workflow level: read-only
permissions:
  contents: read

jobs:
  publish:
    environment: release
    permissions:
      contents: read
      id-token: write # only here, never workflow-wide

The rest of the Actions checklist:

5. Remove publish tokens from CI

The best npm token is the one that does not exist.

If trusted publishing is available for your setup, use it and delete the automation tokens it replaces. A long-lived publish token in CI secrets is the single most valuable thing an attacker can exfiltrate from your repo, and stealers now harvest CI secrets as standard practice.

The npm side of the audit:

6. Treat release files as sensitive

Some files in your repo are not ordinary code. If a file can change what gets published, treat it as release infrastructure.

A CODEOWNERS file makes that surface visible, and combined with branch protection's "require review from code owners", changes to it demand sign-off:

# .github/CODEOWNERS
/.github/workflows/   @your-username
package.json          @your-username
package-lock.json     @your-username
pnpm-lock.yaml        @your-username
yarn.lock             @your-username
npm-shrinkwrap.json   @your-username
rollup.config.*       @your-username
vite.config.*         @your-username
webpack.config.*      @your-username
tsup.config.*         @your-username
/scripts/             @your-username

Even in a small project where you are the only owner, the file documents which paths are dangerous. It says: these files are not ordinary code. Future you, future co-maintainers, and future security reviewers all benefit from that being written down.

7. Watch for snapshot branches

The June 2026 worm used throwaway branches with random names. Most maintainers do not have a SOC, and they do not need one. They need cheap tripwires:

Two free tools cover most of this. Watch your own repo (Watch → Custom → Releases and Discussions plus rule-based notifications), and subscribe to your own package on npm so new-version emails reach you. The cheapest alert that something is wrong is an email about a release you did not make, arriving while you still have account access.

8. Prepare for account lockout

This is the section maintainers need most, and the one nobody writes.

When an account is compromised, platforms suspend it. That is the right call for stopping the spread, and it has a brutal side effect: the maintainer loses access to the security log, the audit trail, the commit history, and the settings pages — the very evidence needed to prove which actions were not theirs, at exactly the moment everyone is asking.

So keep a recovery file outside GitHub: a password-manager secure note, an encrypted local file, even a printed sheet. For each package:

- npm package name
- GitHub repo URL
- npm owner account
- Trusted publisher settings (repo, workflow file, environment name)
- Normal release workflow path
- npm support: https://www.npmjs.com/support
- GitHub support: https://support.github.com
- Emergency contacts (co-maintainers, employer security team if relevant)
- Last known safe version
- The deprecate command:
    npm deprecate <pkg>@<bad-version> "compromised - do not install"
- The supersede command (publish a clean version ABOVE the bad one,
  because deprecation does not stop semver ranges resolving to it)

Add to it the moment anything changes. The file costs you twenty minutes on a quiet afternoon. Its absence costs you days in the middle of the worst week of your maintainer life.

Bottom line

Open source maintainers should not need to become incident response teams overnight.

Until the platforms build better defaults, a small set of controls protects you before the panic starts: phishing-resistant login, a protected release branch, a locked release environment, minimal Actions permissions, no long-lived publish tokens, release files treated as infrastructure, cheap tripwires, and a recovery plan that still works if your GitHub or npm access disappears.

None of this makes a maintainer invincible.

It just means one stolen credential does not immediately become dozens of compromised packages, hundreds of malicious versions, and a maintainer locked out of the audit logs mid-incident.

If you want the deeper dives: the trusted publishing setting that would have blocked the snapshot-branch attacks covers layer three in detail, and the package manager settings that would have blocked the Axios attack covers the consumer side. Publisher, consumer, and now the person in between.

security npm supply-chain github-actions open-source