OTP validator on publish/unpublish/deprecate rejects valid recovery codes — length and pattern enforcement inconsistent
Nobody has claimed this yet.
- Dominant language
- JavaScript
- Stars
- 10.1k
- Forks
- 4.7k
- Avg merge
- 2d 2h
- Merged PRs (30d)
- 19
Description
npm version: 10.9.4
Node version: 22.21.1
Platform: macOS Darwin 25.3.0
Summary
The publish/unpublish/deprecate endpoints' server-side validator for the otp field requires the value to be (a) all digits matching /^\d+$/ AND (b) exactly 64 characters long. This rejects every valid recovery code format that npm itself issues to users (16-char alphanumeric, 48-char hex) but inconsistently accepts 64-char hex strings despite their containing a-f letters.
Reproduction
Account: granular access token in ~/.npmrc, account 2FA enabled with security key only (no TOTP authenticator), org 2FA enforcement on.
# 1. 16-char alphanumeric recovery code (the format npm issues)
npm publish --access public --provenance=false --otp=63a1ae13e3023820
# → 400 Bad Request: "child 'otp' fails because ['otp' with value
# '63a1ae13e3023820' fails to match the required pattern: /^\d+$/,
# 'otp' length must be 64 characters long]"
# 2. 48-char hex recovery code (also a format some npm accounts get)
npm publish --otp=82fa6fa8d54a0b21c29d57f501aab450a13adf47d4db17e0
# → same 400 with both pattern + length errors
# 3. 64-char hex string — accepted, despite containing a-f (violates /^\d+$/)
npm publish --otp=9fd5371d56172164b950daf1ff84a71a4559a00e832038d09ad25055b26003f0
# → SUCCESS
What's wrong
The error message advertises a /^\d+$/ pattern requirement, but the actual server-side enforcement appears to be:
- Pattern (
/^\d+$/): advisory or unenforced — 64-char hex strings pass - Length (
== 64): strictly enforced - Result: only 64-character strings work, regardless of content
But recovery codes from https://www.npmjs.com/settings/<user>/tfa → Manage Recovery Codes are typically 16 or 48 characters, never 64. TOTP codes are 6 digits. Neither matches the working format.
Impact
Accounts that:
- Have security-key-only 2FA (no TOTP authenticator), AND
- Belong to organizations with 2FA enforcement enabled
…cannot publish at all using the recovery codes npm issued them. They're locked out of the documented --otp=<code> path.
Additional CLI quirk
npm unpublish --otp=<value> fails with Usage: npm unpublish [<package-spec>] Options: [--dry-run] [-f|--force] — the --otp flag is not declared in npm unpublish's usage in 10.9.4, but the underlying API call still requires OTP. Workaround:
npm_config_otp=<value> npm unpublish <pkg>@<version> --force
Same broken validator applies once the env-var path bypasses the unrecognized-flag error.
Suggested fix
Either:
- Update the server-side validator to accept the recovery-code formats actually issued (16-char alphanumeric, 48-char hex), OR
- Update the npm web UI to issue only 64-char digit-only codes that match the validator, AND update the validator error message to remove the misleading
/^\d+$/claim, OR - Document the working OTP format publicly — currently neither npm docs nor the error message reveal that 64 chars is the only working length
Workaround used
Locating an old TOTP shared-secret string (64 hex chars) that happens to satisfy the length check, used in place of a recovery code. This is brittle, undocumented, and degrades over time as TOTP secrets are typically rotated.
Related
Filing companion discussion at npm/feedback for the underlying UI gap that forces affected users into this state in the first place: TOTP authenticator cannot be added on accounts with security-key-only 2FA + org enforcement.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the publish, unpublish, and deprecate endpoint validation paths and the npm unpublish option handling described here. Reproduce the listed 16-character, 48-character, and 64-character values; done means issued recovery codes are accepted consistently and unpublish exposes the required OTP path.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, nodejs
- Domain
- authentication, cli, security
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100