Problem
The secret-name heuristic that provides base-cli's defense-in-depth redaction matches only five
stems — token, password, secret, api[-_]?key, authorization — and that same list is
duplicated in three places that must be kept in sync by hand:
SECRET_KEY_RE (lib/python/base_cli/redaction.py:9), used by is_secret_key() for argv
parameter names and inline key=value text;
_is_sensitive_key() (lib/python/base_cli/json_contracts.py:155-156), an inline
re.search over the same alternation, used to redact JSON mapping keys;
_SENSITIVE_ASSIGNMENT (lib/python/base_cli/json_contracts.py:31-34), a third copy used to
redact key: value text inside envelope strings.
Common credential names that operations and infrastructure CLIs actually use are not covered.
Verified evidence
Reviewed 2026-09-30 at a58ec109349fa3f3d03eae5b0de078b39ea361a2. is_secret_key() results:
| Name |
Matched |
token, password, secret, api_key, apikey, authorization |
yes |
secret_access_key |
yes (contains secret) |
passwd |
no |
pwd |
no |
credential, credentials |
no |
private_key |
no |
access_key |
no |
bearer |
no |
passphrase |
no |
signature |
no |
session_id |
no |
cookie |
no |
otp |
no |
--access-key missing is notable: AWS-shaped CLIs pair --access-key-id with
--secret-access-key, and only the second is protected. --passwd/--pwd are conventional short
forms, and --private-key/--passphrase are standard for SSH and TLS material.
This is defense in depth, not the primary mechanism — sensitive=True, hide_input, and
sensitive_parameters are explicit and work correctly — and docs/json-contracts.md does state
the five stems. But the guarantee an adopter infers from "secret-looking parameter names are
protected automatically" is materially wider than what is implemented.
Proposal
- Extract one shared, commented pattern into a single module (
redaction.py) and have both
json_contracts.py call sites consume it. Three hand-synchronized copies of a security-relevant
pattern is the underlying defect.
- Widen the stem list: add
passwd, pwd, passphrase, credential, private[-_]?key,
access[-_]?key, bearer, session, cookie, signature, otp, salt, client[-_]?secret
(already covered by secret), auth[-_]?token (covered), sas, pem.
- Weigh false positives deliberately and record the reasoning:
key alone is too broad
(--key-file, --sort-key, --partition-key), auth alone is borderline. Redacting a
non-secret in a diagnostic log is a much cheaper error than leaking a credential, but the
decision should be written down, not implicit.
- Update
docs/json-contracts.md and docs/security-threat-model.md with the final list and
restate that the heuristic is a backstop, not a substitute for explicit marking.
Acceptance criteria
- One pattern definition; no module contains a second copy of the stem alternation.
- A property or table test asserts the full documented stem list matches in all three positions
(argv parameter name, inline text, JSON key) and that named non-secrets such as --key-file,
--sort-key, and --public-key do not match.
- Docs list the exact stems and the false-positive policy.
Non-goals
- Do not attempt value-shaped secret detection (entropy, provider prefixes) here.
- Do not weaken or replace explicit
sensitive=True marking.
Problem
The secret-name heuristic that provides base-cli's defense-in-depth redaction matches only five
stems —
token,password,secret,api[-_]?key,authorization— and that same list isduplicated in three places that must be kept in sync by hand:
SECRET_KEY_RE(lib/python/base_cli/redaction.py:9), used byis_secret_key()for argvparameter names and inline
key=valuetext;_is_sensitive_key()(lib/python/base_cli/json_contracts.py:155-156), an inlinere.searchover the same alternation, used to redact JSON mapping keys;_SENSITIVE_ASSIGNMENT(lib/python/base_cli/json_contracts.py:31-34), a third copy used toredact
key: valuetext inside envelope strings.Common credential names that operations and infrastructure CLIs actually use are not covered.
Verified evidence
Reviewed 2026-09-30 at
a58ec109349fa3f3d03eae5b0de078b39ea361a2.is_secret_key()results:token,password,secret,api_key,apikey,authorizationsecret_access_keysecret)passwdpwdcredential,credentialsprivate_keyaccess_keybearerpassphrasesignaturesession_idcookieotp--access-keymissing is notable: AWS-shaped CLIs pair--access-key-idwith--secret-access-key, and only the second is protected.--passwd/--pwdare conventional shortforms, and
--private-key/--passphraseare standard for SSH and TLS material.This is defense in depth, not the primary mechanism —
sensitive=True,hide_input, andsensitive_parametersare explicit and work correctly — anddocs/json-contracts.mddoes statethe five stems. But the guarantee an adopter infers from "secret-looking parameter names are
protected automatically" is materially wider than what is implemented.
Proposal
redaction.py) and have bothjson_contracts.pycall sites consume it. Three hand-synchronized copies of a security-relevantpattern is the underlying defect.
passwd,pwd,passphrase,credential,private[-_]?key,access[-_]?key,bearer,session,cookie,signature,otp,salt,client[-_]?secret(already covered by
secret),auth[-_]?token(covered),sas,pem.keyalone is too broad(
--key-file,--sort-key,--partition-key),authalone is borderline. Redacting anon-secret in a diagnostic log is a much cheaper error than leaking a credential, but the
decision should be written down, not implicit.
docs/json-contracts.mdanddocs/security-threat-model.mdwith the final list andrestate that the heuristic is a backstop, not a substitute for explicit marking.
Acceptance criteria
(argv parameter name, inline text, JSON key) and that named non-secrets such as
--key-file,--sort-key, and--public-keydo not match.Non-goals
sensitive=Truemarking.