Skip to main content

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 npm environment.
  • npm accepts publishes only from release.yml in the npm environment 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.
  • main accepts only pull requests with a green ci check from GitHub Actions.
  • Release tags cannot be moved or deleted, and release.yml tags 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.

  1. Acknowledge within a week. Say whether you can reproduce it.
  2. 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.
  3. Release the fix through the normal flow with a patch changeset.
  4. 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.

WhatDo now
npm accountChange the password, check 2FA, revoke all tokens on npmjs.com (Access Tokens), review recent versions of every package.
GitHub account or personal access tokenRevoke the token, sign out other sessions, check SSH keys, OAuth apps and Settings → Security log.
A secret committed to a repositoryRotate it at the provider. Then remove it from the code. Push protection should have blocked it; find out why not.
Vercel account or tokenRevoke 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

  1. Remove the trusted publisher on npmjs.com to stop further publishes.
  2. Deprecate the version with a clear message and point latest at the last good version (npm dist-tag add). Contact npm support to remove it; npm removes malware quickly.
  3. Find how it happened (approved run, compromised account, changed workflow) and close that path.
  4. Publish a GitHub advisory so users get alerts.
  5. 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/main and opens the pull request, but installs and runs nothing from the repository. It fails if the patch changes anything in a package.json besides its version and the ranges of the repository's own packages, or touches anything besides CHANGELOG.md files and changeset files.
  • A third job, which checks out and installs nothing, starts ci on 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.