From 91888057bb4e0fbe17f0abab3214019a3caf9cee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=8C=BE?= Date: Sat, 30 May 2026 15:00:40 +0800 Subject: [PATCH] fix: redact secrets in non-code files (.env, .yaml, .json) Previously, `redact_sensitive_text` was called with `code_file=True` for ALL files in `read_file` and `search_files` tools. This skipped the ENV-assignment and JSON-field regex patterns globally, which meant secrets in config files like `.env`, `.yaml`, `.toml`, and `.json` were not properly redacted. For example, `WEIXIN_TOKEN=abcdef123456` in a `.env` file would only be partially masked (by prefix patterns) instead of fully redacted. Fix: introduce `_is_source_code_file(path)` that checks the file extension against a known set of source-code extensions. Only source-code files (`.py`, `.js`, `.go`, `.rs`, etc.) use `code_file=True`; config and env files use `code_file=False` so that ENV-assignment and JSON-field patterns are applied. This preserves the original intent (avoid false positives in source code like `MAX_TOKENS=100`) while properly protecting secrets in config files. --- tools/file_tools.py | 55 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/tools/file_tools.py b/tools/file_tools.py index 2cedc4bcd5f19..f81aa34e1392d 100644 --- a/tools/file_tools.py +++ b/tools/file_tools.py @@ -20,6 +20,53 @@ logger = logging.getLogger(__name__) +# --------------------------------------------------------------------------- +# Source-code file extensions for redaction heuristics. +# Files with these extensions are treated as source code when calling +# ``redact_sensitive_text(code_file=True)`` — which skips ENV-assignment +# and JSON-field regex patterns to avoid false positives (e.g. MAX_TOKENS=100 +# in a Python file). All other files (including .env, .yaml, .json, .conf, +# .toml, .ini, .cfg, .properties, etc.) use ``code_file=False`` so that +# secret-bearing patterns like ``API_KEY=`` are properly redacted. +# --------------------------------------------------------------------------- +_SOURCE_CODE_EXTENSIONS = frozenset({ + ".py", ".pyi", ".pyx", # Python + ".js", ".mjs", ".cjs", ".jsx", # JavaScript + ".ts", ".tsx", ".mts", # TypeScript + ".go", # Go + ".rs", # Rust + ".java", ".kt", ".kts", ".scala", # JVM + ".c", ".cpp", ".cc", ".cxx", ".h", ".hpp", ".hxx", # C/C++ + ".rb", ".erb", # Ruby + ".php", # PHP + ".swift", # Swift + ".lua", # Lua + ".r", ".R", # R + ".m", ".mm", # Objective-C + ".sh", ".bash", ".zsh", ".fish", # Shell + ".pl", ".pm", # Perl + ".ex", ".exs", # Elixir + ".erl", ".hrl", # Erlang + ".hs", ".lhs", # Haskell + ".ml", ".mli", # OCaml + ".clj", ".cljs", ".cljc", # Clojure + ".dart", # Dart + ".vue", ".svelte", # Frontend frameworks + ".sql", # SQL +}) + + +def _is_source_code_file(path: str) -> bool: + """Return True if *path* looks like a source-code file. + + Used to decide whether ``code_file=True`` should be passed to + ``redact_sensitive_text``. Source-code files get ENV-assignment and + JSON-field patterns skipped to reduce false positives; config/env files + keep those patterns active so secrets are properly masked. + """ + ext = os.path.splitext(path)[1].lower() + return ext in _SOURCE_CODE_EXTENSIONS + _EXPECTED_WRITE_ERRNOS = {errno.EACCES, errno.EPERM, errno.EROFS} @@ -570,7 +617,9 @@ 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, code_file=True) + result.content = redact_sensitive_text( + result.content, code_file=_is_source_code_file(path), + ) result_dict["content"] = result.content # Large-file hint: if the file is big and the caller didn't ask @@ -993,7 +1042,9 @@ 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, code_file=True) + m.content = redact_sensitive_text( + m.content, code_file=_is_source_code_file(m.path), + ) result_dict = result.to_dict() if count >= 3: