From 6f864f8f942b3532bea8e10584024a509bd248b4 Mon Sep 17 00:00:00 2001 From: ms-alan Date: Mon, 27 Apr 2026 00:05:49 +0800 Subject: [PATCH] fix(redact): add code_file param to skip false-positive ENV/JSON patterns ENV-assignment and JSON-field regex patterns in redact_sensitive_text() cause false positives when reading source code files: - MAX_TOKENS=*** triggers the ENV assignment pattern - "apiKey": "test" in test fixtures triggers the JSON field pattern Add code_file=False parameter. When code_file=True, skip only the ENV-assignment and JSON-field regex passes; all other patterns (prefixes, auth headers, private keys, DB connstrings, JWTs, URL secrets) are still applied. Update file_tools.py (read_file and search_files) to pass code_file=True so agent code analysis is not polluted by false-positive redactions. Closes #15934 --- agent/redact.py | 28 +++++++++++++++++----------- tools/file_tools.py | 4 ++-- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/agent/redact.py b/agent/redact.py index 970ad5adfb..afdee65288 100644 --- a/agent/redact.py +++ b/agent/redact.py @@ -305,13 +305,18 @@ def _redact_form_body(text: str) -> str: return _redact_query_string(text.strip()) -def redact_sensitive_text(text: str, *, force: bool = False) -> str: +def redact_sensitive_text(text: str, *, force: bool = False, code_file: bool = False) -> str: """Apply all redaction patterns to a block of text. Safe to call on any string -- non-matching text passes through unchanged. Disabled by default — enable via security.redact_secrets: true in config.yaml. Set force=True for safety boundaries that must never return raw secrets regardless of the user's global logging redaction preference. + + Set code_file=True to skip the ENV-assignment and JSON-field regex + patterns when the text is known to be source code (e.g. MAX_TOKENS=*** + constants, "apiKey": "test" fixtures). Prefix patterns, auth headers, + private keys, DB connstrings, JWTs, and URL secrets are still redacted. """ if text is None: return None @@ -325,17 +330,18 @@ def redact_sensitive_text(text: str, *, force: bool = False) -> str: # Known prefixes (sk-, ghp_, etc.) text = _PREFIX_RE.sub(lambda m: _mask_token(m.group(1)), text) - # ENV assignments: OPENAI_API_KEY=sk-abc... - def _redact_env(m): - name, quote, value = m.group(1), m.group(2), m.group(3) - return f"{name}={quote}{_mask_token(value)}{quote}" - text = _ENV_ASSIGN_RE.sub(_redact_env, text) + # ENV assignments: OPENAI_API_KEY=*** (skip for code files — false positives) + if not code_file: + def _redact_env(m): + name, quote, value = m.group(1), m.group(2), m.group(3) + return f"{name}={quote}{_mask_token(value)}{quote}" + text = _ENV_ASSIGN_RE.sub(_redact_env, text) - # JSON fields: "apiKey": "value" - def _redact_json(m): - key, value = m.group(1), m.group(2) - return f'{key}: "{_mask_token(value)}"' - text = _JSON_FIELD_RE.sub(_redact_json, text) + # JSON fields: "apiKey": "***" (skip for code files — false positives) + def _redact_json(m): + key, value = m.group(1), m.group(2) + return f'{key}: "{_mask_token(value)}"' + text = _JSON_FIELD_RE.sub(_redact_json, text) # Authorization headers text = _AUTH_HEADER_RE.sub( diff --git a/tools/file_tools.py b/tools/file_tools.py index 6022eee912..106bd295be 100644 --- a/tools/file_tools.py +++ b/tools/file_tools.py @@ -570,7 +570,7 @@ def read_file_tool(path: str, offset: int = 1, limit: int = 500, task_id: str = # ── Redact secrets (after guard check to skip oversized content) ── if result.content: - result.content = redact_sensitive_text(result.content) + result.content = redact_sensitive_text(result.content, code_file=True) result_dict["content"] = result.content # Large-file hint: if the file is big and the caller didn't ask @@ -993,7 +993,7 @@ def search_tool(pattern: str, target: str = "content", path: str = ".", if hasattr(result, 'matches'): for m in result.matches: if hasattr(m, 'content') and m.content: - m.content = redact_sensitive_text(m.content) + m.content = redact_sensitive_text(m.content, code_file=True) result_dict = result.to_dict() if count >= 3: