Security model
What apm protects against, how accounts, tokens and second factors work, and how apm makes your package manager install exactly what it checked.
apm assumes three things go wrong: a publishing token leaks, an account is taken over, and a package is malicious from the start. It answers each in a different place:
| Threat | apm’s answer |
|---|---|
| A token leaks | A login token can upload but not release. A person approves every version with a passkey or an authenticator code. |
| An account is taken over | Revoking, restoring, incident response and owner changes need a fresh second factor. apm incident revokes every token and recent version at once. |
| A package is malicious | The score, the cooldown and the AI review flag it before install; yank and revoke pull it after. |
| A config file or lockfile points elsewhere | apm add pins every scope to the registry it checked and verifies what was installed. |
Accounts and scopes
You sign in with GitHub. Your scope is your GitHub login, bound to GitHub’s numeric account id, and permanent once claimed. A GitHub rename does not move or free it. See Names and scopes.
Tokens
The token apm login gives you is a staging token:
- A version it uploads is staged. It goes live only when you approve it on apmjs.dev.
- Moving dist-tags, deprecating, yanking and unyanking need a fresh code with it (
--otp). - Revoke, restore, incident response and owner changes need a fresh second factor with any token.
apm stores the token in your user .npmrc with mode 0600, and sends APM_TOKEN only to the registry it was issued for. It removes credentials from the environment of install scripts and package binaries it runs.
Second factors
Codes and passkeys protect every action that changes what people install or how you sign in.
- One action, once. A code or passkey check on the website is bound to your browser session, the action and every parameter of it: which upload, which CLI command, which review decision and note, which evidence item and reason, which sign-in setting. It confirms that action once, within 5 minutes, and nothing else. Signing in with a passkey confirms nothing.
- CLI commands. Revoke, restore, incident response and owner changes take
--otp <code>. Without it, the error links to a page that approves exactly that command, once, for 10 minutes. - Codes. A code is six digits and works once. Wrong codes and recovery codes share one budget per account: five failures lock checks for 15 minutes, on the website and the CLI together.
- Changing factors. Turning off 2FA, adding a passkey or linking another sign-in needs your current code, a recovery code, or a passkey check made for that change. The authenticator secret is never shown again after setup. Recovery codes are shown once and each works once.
- Website changes come from the website. Every change a website session can make is refused with 403 unless the request carries the website’s
Origin, before any code is checked. Requests made with a CLI or CI token are not affected.
What the registry serves
- Package metadata comes from the
package.jsoninside the tarball, not from the publish request, so a request cannot hide install scripts the tarball has. - Versions are immutable. A tarball is checked against its published sha512 before it is served, including npm tarballs apm caches.
- Answers that belong to one user (account, sessions, staged uploads, tokens, login) are marked
Cache-Control: private, no-store, so no shared cache keeps them. - Revoked versions answer 410 and appear as critical advisories to
bun auditandnpm audit.
How apm add installs what it checked
A check means nothing if the package manager then installs other bytes. apm add:
- pins every scope in the checked dependency tree to the checked registry, and refuses if an
.npmrc,bunfig.tomlor.yarnrc.ymlsends one of those scopes elsewhere; - refuses if the lockfile already pins an added package to a different tarball;
- installs with every install script held back (
--ignore-scripts); - reads the lockfile the install wrote and compares every package’s integrity with what the checked registry serves;
- runs the held-back scripts only when everything matches. Otherwise it restores
package.jsonand the lockfile, removes the unverified packages and exits non-zero.
Limits: with yarn, apm does steps 1 and 2 only. apm x does step 1 only, because bunx and npx install into their own cache. The tree walk stops at 300 packages; the rest is listed as not checked.
What apm does not protect against
- Code that only shows what it does at runtime. See what the score cannot catch.
- A compromised build machine that publishes clean-looking code with a backdoor, and a person who approves it.
- Packages installed with a stock client pointed at another registry, or with no apm settings at all.
Self-hosting
A self-hosted registry can turn on npm password accounts with APM_LEGACY_AUTH=1. It refuses to start when its public URL is a public https address, unless you also set APM_ALLOW_LEGACY_IN_PROD=1. See Configuration.
Reporting
Report a vulnerability in apm privately; see Report a vulnerability in apm.