From 64cb4cef4e87394c2ee8606735e5be2710ec85c0 Mon Sep 17 00:00:00 2001 From: nesquena-hermes Date: Thu, 25 Jun 2026 17:58:12 +0000 Subject: [PATCH] fix(#4926): env-assignment redaction must not eat URL query tails MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Codex re-gate: the env/AUTH '=' redaction regex matched 'token=' INSIDE a URL (?token=...&mode=debug&signature=...) before the dedicated URL-query redactor, and its \S+ value swallowed the rest of the URL incl later params + closing quote. Anchor the env-assignment match to a shell-token boundary (^|[\s;|(]) so it can't match keys preceded by ? or & — URL query params are left to the dedicated query-param redactor, which masks the secret-looking ones while preserving the command tail. + regression tests (URL-tail-preserved, benign-PATH-not-over-redacted). --- static/ui.js | 4 ++-- tests/test_issue4926_shell_full_command.py | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/static/ui.js b/static/ui.js index 0eed0295..7402cb89 100644 --- a/static/ui.js +++ b/static/ui.js @@ -12525,10 +12525,10 @@ function _redactToolTargetLabel(value){ // the expanded shell card can't leak a key on a non-first line (#4926). Keys // matched case-insensitively: *(TOKEN|API_KEY|APIKEY|SECRET|PASSWD|PASSWORD| // ACCESS_KEY|PRIVATE_KEY|AUTH|CREDENTIAL|SESSION_KEY|CLIENT_SECRET)*. - .replace(/(\b[A-Za-z0-9_]*(?:TOKEN|API[_-]?KEY|SECRET|PASSWD|ACCESS[_-]?KEY|PRIVATE[_-]?KEY|CREDENTIALS?|CLIENT[_-]?SECRET|SESSION[_-]?KEY)[A-Za-z0-9_]*\s*=\s*)(?:"[^"]*"|'[^']*'|\S+)/gi,'$1[redacted]') + .replace(/(^|[\s;|(])([A-Za-z0-9_]*(?:TOKEN|API[_-]?KEY|SECRET|PASSWD|ACCESS[_-]?KEY|PRIVATE[_-]?KEY|CREDENTIALS?|CLIENT[_-]?SECRET|SESSION[_-]?KEY)[A-Za-z0-9_]*\s*=\s*)(?:"[^"]*"|'[^']*'|\S+)/gi,'$1$2[redacted]') // AUTH-family env assignment, but only the `=` form (the `Authorization:` // header colon form is handled separately below, and must not be eaten here). - .replace(/(\b[A-Za-z0-9_]*AUTH[A-Za-z0-9_]*\s*=\s*)(?:"[^"]*"|'[^']*'|\S+)/gi,'$1[redacted]') + .replace(/(^|[\s;|(])([A-Za-z0-9_]*AUTH[A-Za-z0-9_]*\s*=\s*)(?:"[^"]*"|'[^']*'|\S+)/gi,'$1$2[redacted]') // --token / --api-key / --secret style flags. .replace(/(--(?:token|api[_-]?key|secret|access[_-]?key|client[_-]?secret|auth[_-]?token)(?:=|\s+))(?:"[^"]*"|'[^']*'|\S+)/gi,'$1[redacted]') // Authorization: Bearer/Bot/Token (header or curl -H form): diff --git a/tests/test_issue4926_shell_full_command.py b/tests/test_issue4926_shell_full_command.py index 0b7b7809..c213457f 100644 --- a/tests/test_issue4926_shell_full_command.py +++ b/tests/test_issue4926_shell_full_command.py @@ -152,3 +152,22 @@ def test_password_redaction_still_works(driver_path): assert "hunter2" not in out["lead"] assert "topsecret" not in out["lead"] assert "[redacted]" in out["lead"] + + +def test_url_query_redaction_preserves_command_tail(driver_path): + """#4926 gate: redacting a secret URL query param must NOT swallow the rest + of the URL (later params + closing quote). Regression for the env-assignment + regex over-matching `token=` inside a URL and eating the tail with \\S+.""" + out = _run(driver_path, {"args": {"command": "curl 'https://x.com/cb?token=tok_leak&mode=debug&signature=sig_leak'"}}) + lead = out["lead"] + assert "tok_leak" not in lead and "sig_leak" not in lead + # The non-secret param + closing quote must survive (no tail truncation). + assert "mode=debug" in lead + assert lead.rstrip().endswith("'") + + +def test_benign_assignment_not_over_redacted(driver_path): + """A non-secret assignment like PATH= must not be mangled.""" + out = _run(driver_path, {"args": {"command": "export PATH=/usr/bin:/bin\necho ok"}}) + assert "/usr/bin:/bin" in out["lead"] + assert "echo ok" in out["lead"]