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.
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
otpfield 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 containinga-fletters.Reproduction
Account: granular access token in
~/.npmrc, account 2FA enabled with security key only (no TOTP authenticator), org 2FA enforcement on.What's wrong
The error message advertises a
/^\d+$/pattern requirement, but the actual server-side enforcement appears to be:/^\d+$/): advisory or unenforced — 64-char hex strings pass== 64): strictly enforcedBut recovery codes from
https://www.npmjs.com/settings/<user>/tfa → Manage Recovery Codesare typically 16 or 48 characters, never 64. TOTP codes are 6 digits. Neither matches the working format.Impact
Accounts that:
…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 withUsage: npm unpublish [<package-spec>] Options: [--dry-run] [-f|--force]— the--otpflag is not declared innpm unpublish's usage in 10.9.4, but the underlying API call still requires OTP. Workaround:Same broken validator applies once the env-var path bypasses the unrecognized-flag error.
Suggested fix
Either:
/^\d+$/claim, ORWorkaround 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.