Skip to content

Redaction fallback misses secrets embedded in query strings, comma-separated lists, and header-style values #211

Description

@codeforester

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.

Metadata

Metadata

Assignees

Labels

securitySecurity hardening or vulnerability work

Type

No type

Projects

Status
Done

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions