Summary
The generic fallback redaction layer (_redact_inline_text), meant to catch secrets in option values that aren't covered by name/position-based redaction, only recognizes a secret if it is the entire value of a key=value token split on the first =. It never inspects :-delimited values. As a result, secrets embedded inside a larger string — a URL query parameter, a comma-separated list, or an HTTP header value — pass through completely unredacted into logs and the persisted history file, even when the embedded key name is one of the exact patterns (token, password, secret, api[-_]?key, authorization) the redaction heuristic is designed to catch.
Details
lib/python/base_cli/redaction.py:595-599:
def _redact_inline_text(value: str) -> str:
key, separator, _raw_value = value.partition("=")
if separator and is_secret_key(option_name_to_parameter(key)):
value = f"{key}={REDACTED}"
return redact_text_value(value)
Reproduced directly against redact_argv (unmodified, current code):
redact_argv(['mycli', 'fetch', '--url',
'https://api.example.com/v1/resource?filter=active&token=SUPERSECRET123'], set())
# -> token=SUPERSECRET123 passes through unredacted (the first '=' pair is 'filter', not 'token')
redact_argv(['mycli', 'run', '--env', 'FOO=bar,SECRET_TOKEN=hunter2'], set())
# -> SECRET_TOKEN=hunter2 passes through unredacted
redact_argv(['mycli', 'call', '-H', 'Authorization: Bearer sk-supersecrettoken123'], set())
redact_argv(['mycli', 'call', '--header', 'Authorization: Bearer sk-supersecrettoken123'], set())
# -> both pass through completely unredacted, despite "Authorization" being one of SECRET_KEY_RE's own patterns
Impact
docs/security-threat-model.md's mitigation table for "Secrets in argv... leak into logs" specifically cites "secret-name heuristics" as covering this class of risk, with the documented residual risk limited to "a custom secret name... can still disclose data." The Authorization header case above shows the gap is broader than that: even a recognized secret-name pattern doesn't get redacted once it's embedded inside a larger token rather than being the whole value. Any CLI built on this framework that accepts URLs with query parameters or -H/--header-style options (both extremely common for CLIs that wrap HTTP calls) will write live credentials into its persistent log/history files whenever a teammate later greps, shares, or attaches those files for support.
Suggested fix
Extend _redact_inline_text (or the underlying scan) to check every key=value-shaped segment in a token, not just the first, and to also recognize key: value (colon-delimited, HTTP-header-style) segments — not only key=value.
Summary
The generic fallback redaction layer (
_redact_inline_text), meant to catch secrets in option values that aren't covered by name/position-based redaction, only recognizes a secret if it is the entire value of akey=valuetoken split on the first=. It never inspects:-delimited values. As a result, secrets embedded inside a larger string — a URL query parameter, a comma-separated list, or an HTTP header value — pass through completely unredacted into logs and the persisted history file, even when the embedded key name is one of the exact patterns (token,password,secret,api[-_]?key,authorization) the redaction heuristic is designed to catch.Details
lib/python/base_cli/redaction.py:595-599:Reproduced directly against
redact_argv(unmodified, current code):Impact
docs/security-threat-model.md's mitigation table for "Secrets in argv... leak into logs" specifically cites "secret-name heuristics" as covering this class of risk, with the documented residual risk limited to "a custom secret name... can still disclose data." TheAuthorizationheader case above shows the gap is broader than that: even a recognized secret-name pattern doesn't get redacted once it's embedded inside a larger token rather than being the whole value. Any CLI built on this framework that accepts URLs with query parameters or-H/--header-style options (both extremely common for CLIs that wrap HTTP calls) will write live credentials into its persistent log/history files whenever a teammate later greps, shares, or attaches those files for support.Suggested fix
Extend
_redact_inline_text(or the underlying scan) to check everykey=value-shaped segment in a token, not just the first, and to also recognizekey: value(colon-delimited, HTTP-header-style) segments — not onlykey=value.