Skip to content

fix(config): redact credentials in proxy env vars and empty-password userinfo - #10221

Open
itsgaldoron wants to merge 1 commit into
npm:latestfrom
itsgaldoron:fix-config-redact-proxy-env-credentials
Open

itsgaldoron wants to merge 1 commit into
npm:latestfrom
itsgaldoron:fix-config-redact-proxy-env-credentials

Conversation

@itsgaldoron

@itsgaldoron itsgaldoron commented Nov 4, 2026 •

Copy link
Copy Markdown

fix(config): redact credentials in proxy env vars and empty-password userinfo

Fixes #9773.

Root cause

npm config list displays credentials in two ways:

  1. The ; environment-related config section echoes the raw values of the
    HTTP_PROXY / HTTPS_PROXY / NO_PROXY environment variables with
    JSON.stringify(process.env[key]), bypassing the redact() helper that the
    rest of the output uses. A proxy URL with a password is printed verbatim
    (issue [BUG] npm config list leaks credentials in proxy-related environment variables #9773's repro).

  2. Everything redact() covers still leaks one credential shape: userinfo with
    an empty password, e.g. https://ci-token:@proxy.example.com — the usual
    "token as the userinfo" CI/proxy form. @npmcli/redact's
    redactUrlPasswordMatcher only fires when url.password is truthy, so this
    value passes through unchanged and is printed in both the config-value
    display and the env-var section.

Fix

  • Route the env-var section through redact() like the config-value display
    already does.
  • Add redactUserinfo() and compose it with redact() at every config list
    display point (displayVar, the env-var section) and in isPrivate(), so
    config list --json omits the value and config get rejects it exactly like
    any other credential-bearing value (user:pass@… URLs already behave this
    way). When a URL has userinfo and no password, the userinfo is masked to
    ***@ (matching the existing user:***@ convention for non-empty passwords).

Behavior change (what changes for users)

isPrivate() now also fires on a URL that carries a username and an empty
password, so such a value is treated exactly like any other credential-bearing
value. Measured on a clean pair of trees (base 10ffa9f vs this PR):

value before after
proxy=https://mytoken:@proxy.example.com displayed verbatim, present in config list --json, config get proxy returns it proxy = "https://***@proxy.example.com", omitted from --json, config get proxy errors The proxy option is protected
proxy=http://user:pass@example.com:80 http://user:***@example.com:80 unchanged (control)
https-proxy=https://secure-proxy.example.com:8443 (no userinfo) unchanged unchanged (control)
proxy=https://anonymous:@proxy.example.com (username, no secret) displayed verbatim, config get returns it masked to https://***@proxy.example.com, config get rejects it

That last row is the honest edge of the change: any username-plus-empty-password
URL is now private, not only ones whose username looks like a token. We think that
is the right trade — the shape is indistinguishable from a credential at display
time — but it is a real behavior change for anyone storing a username-only URL in
npm config, and config get will now refuse to print it back.

Repro / test output (fail-before, on unpatched latest @ 10ffa9f)

$ node_modules/.bin/tap --no-coverage test/lib/commands/config.js
    not ok 5 - config list redacts credentials in proxy environment variables
        not ok 1 - password is not printed
          ; HTTP_PROXY = "http://proxy-user:proxy-password@proxy.example.com:80"
        not ok 3 - empty-password userinfo token is not printed
    not ok 6 - config list redacts empty-password userinfo in config values
        not ok 1 - token-as-username is not printed
        not ok 4 - config get rejects the credential-bearing value like any other private value
    # failed 3 of 35 tests

Pass-after (with this patch)

$ node_modules/.bin/tap --no-coverage test/lib/commands/config.js
    ok 5 - config list redacts credentials in proxy environment variables
    ok 6 - config list redacts empty-password userinfo in config values
    ok 7 - config list --json omits empty-password userinfo values
    1..35

Full unit suite both sides (tap --no-coverage, 124 suites):
unpatched Suites: 124 passed · Asserts: 6036 passed, 7 skip, of 6043;
patched Suites: 124 passed · Asserts: 6047 passed, 7 skip, of 6054 — the
delta is exactly the 11 new assertions, zero regressions.

Control

NO_PROXY=localhost,127.0.0.1 and https-proxy=https://secure-proxy.example.com:8443
(no userinfo) are asserted unchanged in the same tests, so the redaction only
touches values that actually carry userinfo credentials.

What we are NOT claiming

  • We are not changing @npmcli/redact itself: the empty-password userinfo gap
    exists in the library's redactUrlPasswordMatcher and is worth fixing
    upstream for every consumer. This PR makes npm config list (issue [BUG] npm config list leaks credentials in proxy-related environment variables #9773's
    surface) safe either way.
  • No change to log redaction, error output, or npm config get for values that
    do not carry credentials.

Supersede disclosure

This supersedes #9774 (thank you @tianrking for the original diagnosis; we posted
a takeover note there and will close this PR in favour of theirs if they want to
land it themselves), which
applies redact() to the env-var section. That patch is necessary but not
sufficient: with it applied, https://ci-token:@proxy.example.com is still
printed verbatim in both display sections. This PR keeps that fix and adds the
missing userinfo handling plus tests for both credential shapes. No other open
PR touches this surface (#9432 redacts argv logs, #10041/#9874 fix JSON output
escaping, #10072 redacts error details — different root causes).

🤖 Generated with Claude Code

@itsgaldoron
itsgaldoron requested a review from a team as a code owner November 4, 2026 10:37
…userinfo

The environment-related config section of `npm config list` echoed the raw
values of HTTP_PROXY/HTTPS_PROXY/NO_PROXY, bypassing the redact() helper used
by the rest of the output (issue npm#9773).

Additionally, redact() leaves userinfo with an empty password untouched
(`https://ci-token:@host`, the usual token-as-username CI proxy shape), so
such values were printed verbatim in both the config-value display and the
env-var section. Compose a redactUserinfo() step with redact() at every
`config list` display point and in isPrivate(), so config list --json omits
the value and config get rejects it like any other credential-bearing value.

Regression tests cover both credential shapes plus credential-less controls.

Signed-off-by: Doron Gal <dorongal1@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] npm config list leaks credentials in proxy-related environment variables

1 participant