From eb4bba0b2d9114ef02ac74b93d7a66da2166fa2c Mon Sep 17 00:00:00 2001 From: kshitijk4poor <82637225+kshitijk4poor@users.noreply.github.com> Date: Sat, 3 Oct 2026 20:45:44 +0530 Subject: [PATCH] fix(debug): only treat a masked scheme as one before a token-shaped key Review finding (BR1 r3 2c Medium / 2ab Low): the `***` arm added to _BARE_HEADER_RE treated ANY same-line word after an already-masked value as the secret, so benign prose was eaten and every re-scrub ate one more word (`x-api-key: *** see docs for details` -> `... docs for details` -> `... details`; same for `Authorization: *** then prose`, `X-Api-Key: *** (set in env)`). The scrub was not a fixed point. Require the follow-on value to look like a credential (>=8 token chars, optional `=` padding, then a token boundary) before `***` counts as a masked scheme. The floor is 8, not 16, so 15-char keys still redact. The 336-case header sweep stays at 0 leaks / 0 unstable, the 18 `name: ` cases are still redacted, and the benign lines are stable over three passes. --- hermes_cli/debug_redaction.py | 6 +++--- tests/tui_gateway/test_diagnostics_share_nous.py | 3 +++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/hermes_cli/debug_redaction.py b/hermes_cli/debug_redaction.py index 99459d1db9..67b965fc64 100644 --- a/hermes_cli/debug_redaction.py +++ b/hermes_cli/debug_redaction.py @@ -54,9 +54,9 @@ _QUOTED_HEADER_RE = re.compile( _BARE_HEADER_RE = re.compile( rf"(?P\b(?:{_HEADER_NAME_PATTERN})\b\s*[:=]\s*)" # agent.redact masks the scheme of ``x-api-key: Basic `` to ``***`` and - # keeps the key, so ``***`` before a same-line value (not another secret - # header) is a scheme too, and the key after it is the value. - r"(?P(?>(?:(?:Bearer|Basic|Digest)\s+|\*\*\*[ \t]+(?=[^\s,}\]])" + # keeps the key, so ``***`` before a same-line token-shaped value (not prose + # and not another secret header) is a scheme too, and the key is the value. + r"(?P(?>(?:(?:Bearer|Basic|Digest)\s+|\*\*\*[ \t]+(?=[A-Za-z0-9._~+/-]{8,}=*(?![A-Za-z0-9._~+/=-]))" rf"(?!(?:{_HEADER_NAME_PATTERN})\b\s*[:=]))?)(?!\[REDACTED\])[^\s,}}\]]+)", re.IGNORECASE, ) diff --git a/tests/tui_gateway/test_diagnostics_share_nous.py b/tests/tui_gateway/test_diagnostics_share_nous.py index d508b2a0d6..2c88d2ecc9 100644 --- a/tests/tui_gateway/test_diagnostics_share_nous.py +++ b/tests/tui_gateway/test_diagnostics_share_nous.py @@ -189,6 +189,9 @@ def test_redacted_support_egress_scrubs_structured_values_and_errors( assert "[REDACTED]]" not in failure["error"] assert failure["error"].endswith(" --header X-API-Key Basic [REDACTED]") assert real_redact(failure["error"]) == failure["error"] + # Prose after an already-masked value is not a key: kept, and a fixed point. + benign = real_redact("x-api-key: *** see docs") + assert benign.endswith(" see docs") and real_redact(benign) == benign def test_share_nous_linkless_success_is_a_failure(monkeypatch):