Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 53 additions & 2 deletions tools/file_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -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=<value>`` 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}

Expand Down Expand Up @@ -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(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Current main uses file_read=True for all file-content paths (tools/file_tools.py:1279,1413,2003), which enables the non-reusable prefix-secret sentinel in agent/redact.py:541-547. Replacing it with code_file=... here would restore reusable head/tail masks for config credentials; preserve the file_read security behavior when rebasing this change.

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
Expand Down Expand Up @@ -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:
Expand Down