-
Notifications
You must be signed in to change notification settings - Fork 0
Switch npm platform claims from token verification to signed-claim model (matching PyPI) #107
Description
Problem
npm platform claims currently use token verification via registry.npmjs.org/-/whoami. This is vulnerable to the same attack vector as the LiteLLM supply-chain compromise (March 24, 2026): an attacker who steals an npm token from CI can verify as the maintainer and claim their namespace on auths.
The LiteLLM attacker stole PyPI credentials from a compromised Trivy GitHub Action in CI/CD. If the same pattern targets npm tokens (which are routinely stored as CI secrets), the current npm claim flow would let the attacker claim namespaces under the real maintainer's identity.
Current npm flow (vulnerable)
- User runs
auths id claim npm - CLI prompts for npm access token
- CLI calls
HttpNpmAuthProvider::verify_token()→GET registry.npmjs.org/-/whoamiwith Bearer auth - Server receives the token, independently verifies via npm whoami, stores the claim
- Attack: stolen npm token → attacker verifies as maintainer → claims namespace
PyPI flow (secure, already implemented)
- User runs
auths id claim pypi - CLI prompts for PyPI username (no token)
- CLI creates a signed platform claim with the device signing key (in platform keychain, not CI)
- Server verifies the Ed25519 signature only — trusts the self-reported username
- At namespace claim time, the PyPI verifier checks the public
pypi.org/pypi/{package}/jsonAPI for maintainer status - Attack blocked: stolen PyPI token → attacker can't produce a valid signed claim (needs the device key from macOS Keychain)
Proposed change
Switch npm to the same model as PyPI:
CLI (crates/auths-cli/src/commands/id/claim.rs)
- Remove token prompt from
ClaimPlatform::Npmhandler - Prompt for npm username instead
- Remove
HttpNpmAuthProviderusage
SDK (crates/auths-sdk/src/workflows/platform.rs)
- Remove
npm_tokenparameter fromclaim_npm_identity() - Change proof URL format from
npm-token:{token}:{claim}tonpm-claim:{claim}
Infra (crates/auths-infra-http/src/npm_auth.rs)
- Delete
HttpNpmAuthProvider(dead code after this change) - Remove from
lib.rsexports
Server (auths-cloud/crates/auths-registry-server/src/services/proof_verification.rs)
- Replace
verify_npm_token_proof()withverify_npm_claim_proof()(signature-only, no whoami call) - Update proof dispatch in
routes/identity.rsto matchnpm-claim:prefix
Server (auths-cloud/crates/auths-registry-server/src/routes/identity.rs)
- Update proof URL prefix check from
npm-token:tonpm-claim: - Update
proof_typeDB value from"npm-token-proof"to"npm-claim-proof"
Security model after change
| Layer | What it checks | Can stolen token bypass? |
|---|---|---|
| Platform claim | Ed25519 signature from device key | No — key is in platform keychain |
| Namespace claim | Public npm registry maintainers API | No — can't fake the public API |
The npm namespace verifier (npm_verifier.rs) already checks registry.npmjs.org/{package} for maintainers — no changes needed there.
Why this is more secure
The LiteLLM attack chain was: compromised CI tool → stolen PyPI token → malicious publish. The same chain with npm tokens is: compromised CI tool → stolen npm token → auths id claim npm with stolen token → claim namespace.
With the signed-claim model, the attacker needs the device signing key (not stored in CI, lives in macOS Keychain / Linux Secret Service) to produce a valid platform claim. Stealing an npm token from CI gives them nothing.
Files to modify
| File | Change |
|---|---|
crates/auths-cli/src/commands/id/claim.rs |
Prompt for username, remove token prompt |
crates/auths-sdk/src/workflows/platform.rs |
Remove npm_token param, change proof format |
crates/auths-infra-http/src/npm_auth.rs |
Delete file |
crates/auths-infra-http/src/lib.rs |
Remove HttpNpmAuthProvider export |
auths-cloud/.../services/proof_verification.rs |
Replace verify_npm_token_proof with signature-only verifier |
auths-cloud/.../routes/identity.rs |
Update prefix check and proof_type |