From 1f5e2d0d778ab86c55e35ed3c4d3359a175552bf Mon Sep 17 00:00:00 2001 From: James Golovich Date: Fri, 24 Jul 2026 20:52:01 -0700 Subject: [PATCH 1/2] Add client_secret redaction pass to _url.redact() Add a regex pass that masks OAuth client_secret values in form-encoded and header-style formats. --- vulnhunter-agent/agent/_url.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/vulnhunter-agent/agent/_url.py b/vulnhunter-agent/agent/_url.py index 1ddbe17..6ceb934 100644 --- a/vulnhunter-agent/agent/_url.py +++ b/vulnhunter-agent/agent/_url.py @@ -23,6 +23,9 @@ _RAW_TOKEN_RE = re.compile( r"(ghp_|gho_|ghu_|ghs_|ghr_|github_pat_|sk-ant-)[A-Za-z0-9_-]+" ) +_FORM_SECRET_RE = re.compile( + r"(?i)(client_secret\s*[=:]\s*)[^\s,}&\"']+" +) def redact(text: str) -> str: @@ -42,6 +45,7 @@ def redact(text: str) -> str: s = _BEARER_RE.sub(r"\1***", s) s = _QUERY_TOKEN_RE.sub(r"\1***", s) s = _RAW_TOKEN_RE.sub(r"\1***", s) + s = _FORM_SECRET_RE.sub(r"\1***", s) # Residual risk (VULN-012, CWE-532): redaction is pattern-based over # enumerated token formats; a novel/unknown secret format not in the pass # list above would still pass through to the audit stream. From 52c7bf34d09d229c963586492fbd066c3aca9a8f Mon Sep 17 00:00:00 2001 From: James Golovich Date: Sat, 1 Aug 2026 16:58:10 -0700 Subject: [PATCH 2/2] Handle quoted and alternate-spelling client_secret forms Widen the regex to consume optional surrounding quotes and match client-secret / clientSecret spellings. Add parametrized test covering form-encoded, JSON, TOML, and single-quoted forms with idempotence assertion. --- vulnhunter-agent/agent/_url.py | 2 +- .../tests/verify_012_audit_redaction.py | 23 +++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/vulnhunter-agent/agent/_url.py b/vulnhunter-agent/agent/_url.py index 6ceb934..3482123 100644 --- a/vulnhunter-agent/agent/_url.py +++ b/vulnhunter-agent/agent/_url.py @@ -24,7 +24,7 @@ r"(ghp_|gho_|ghu_|ghs_|ghr_|github_pat_|sk-ant-)[A-Za-z0-9_-]+" ) _FORM_SECRET_RE = re.compile( - r"(?i)(client_secret\s*[=:]\s*)[^\s,}&\"']+" + r"(?i)(client[_-]?secret\"?\s*[=:]\s*)[\"']?[^\s,}&\"']+[\"']?" ) diff --git a/vulnhunter-agent/tests/verify_012_audit_redaction.py b/vulnhunter-agent/tests/verify_012_audit_redaction.py index 1e1d27f..9ef4cea 100644 --- a/vulnhunter-agent/tests/verify_012_audit_redaction.py +++ b/vulnhunter-agent/tests/verify_012_audit_redaction.py @@ -5,6 +5,8 @@ headers, access_token query params, and raw token prefixes. """ +import pytest + from agent._url import redact @@ -38,6 +40,27 @@ def test_raw_token_prefixes_redacted_prefix_preserved(): assert prefix in out, f"prefix {prefix} should be preserved for triage" +@pytest.mark.parametrize( + "text", + [ + "client_secret=SUPERSECRET&grant_type=x", + "client_secret: SUPERSECRET", + "CLIENT_SECRET=SUPERSECRET", + '{"client_secret": "SUPERSECRET"}', + '{"client_secret":"SUPERSECRET"}', + 'client_secret = "SUPERSECRET"', + "client_secret='SUPERSECRET'", + "client-secret=SUPERSECRET", + "clientSecret=SUPERSECRET", + ], +) +def test_client_secret_redacted(text): + out = redact(text) + assert "SUPERSECRET" not in out + assert "***" in out + assert redact(out) == out # idempotent + + def test_benign_text_unchanged(): assert redact("just a normal log line about issue #42") == ( "just a normal log line about issue #42"