Security reports and incidents
Handle a vulnerability report, and what to do when a credential, account or dependency may be compromised.
What protects a release
- Publishing needs the maintainer's approval in the
npmenvironment. - npm accepts publishes only from
release.ymlin thenpmenvironment of the right repository (trusted publishing), and the packages disallow tokens. There is no npm token anywhere to steal. - The publish job does not check out or install anything, so a compromised dependency cannot run code while publish rights exist.
- Every version after the first carries npm provenance that links it to the exact commit and workflow run.
mainaccepts only pull requests with a greencicheck from GitHub Actions.- Release tags cannot be moved or deleted, and
release.ymltags only the commit it packed. - The release run lists every change to the release workflows and scripts since the last release, next to the approval.
- The maintainer's GitHub and npm recovery codes are stored in a password manager. With one maintainer, they are the only way back into the accounts.
Keep these properties. Everything else in this page is what to do when something goes wrong.
A vulnerability report arrives
Reports come through GitHub private vulnerability reporting (Security →
Advisories). .github/SECURITY.md in each repository says so and lists the supported
versions: the latest major.
- Acknowledge within a week. Say whether you can reproduce it.
- Work in the draft advisory. For a serious issue, use its temporary private fork for the fix, so the fix is not public before the release. For a minor issue, a normal pull request with a neutral title is fine.
- Release the fix through the normal flow with a
patchchangeset. - Publish the advisory with the fixed version and request a CVE from the advisory page. Credit the reporter if they want it.
Do not discuss an unfixed vulnerability in a public issue. If someone opens one, copy the details into a draft advisory and delete the issue.
A credential may be exposed
Assume it is real. Do the provider step first; removing the value from Git does not revoke anything.
| What | Do now |
|---|---|
| npm account | Change the password, check 2FA, revoke all tokens on npmjs.com (Access Tokens), review recent versions of every package. |
| GitHub account or personal access token | Revoke the token, sign out other sessions, check SSH keys, OAuth apps and Settings → Security log. |
| A secret committed to a repository | Rotate it at the provider. Then remove it from the code. Push protection should have blocked it; find out why not. |
| Vercel account or token | Revoke tokens in Vercel Account → Tokens. CI does not need any Vercel token. |
Then check what happened during the exposure window: npm versions published,
GitHub releases and tags, workflow runs, changes to rulesets, environments and
secrets. The audit (pnpm audit:repos) shows the current settings quickly.
A malicious version was published
- Remove the trusted publisher on npmjs.com to stop further publishes.
- Deprecate the version with a clear message and point
latestat the last good version (npm dist-tag add). Contact npm support to remove it; npm removes malware quickly. - Find how it happened (approved run, compromised account, changed workflow) and close that path.
- Publish a GitHub advisory so users get alerts.
- Restore the trusted publisher and release a clean version through the normal flow.
A dependency is compromised
Check whether the bad version is in any lockfile (pnpm why <package>). If
the quarantine held it back, there is nothing to do. If it was installed,
treat any machine or CI run that installed it as exposed: rotate the
credentials that were reachable there. In CI, jobs that install dependencies
never hold publish rights, and none of them can push. release.yml splits the
Version packages pull request into three jobs:
- Prepare version runs dependency code (the Changesets CLI and its changelog plugin) with a read-only token. A compromised dependency there can only change the patch it uploads.
- Version packages holds the token that pushes
changeset-release/mainand opens the pull request, but installs and runs nothing from the repository. It fails if the patch changes anything in apackage.jsonbesides its version and the ranges of the repository's own packages, or touches anything besidesCHANGELOG.mdfiles and changeset files. - A third job, which checks out and installs nothing, starts
cion that branch.
A tampered patch can therefore change only versions, changelogs and changeset
files; a changed script, dependency or files entry fails the job. A tampered
changelog still reaches the pull request, and code reaches main only when a
person merges it, so look at the Version packages diff before you merge. Check recent branches and the
Version packages pull request for unexpected changes.