ReadFileTool shells out for ranged reads (start_line > 1 or end_line is set) instead of reading the file directly.
| cmd=f"cat -n '{path}' | sed -n '{sed_range}p'" |
| res=awaitself._environment.execute(cmd) |
As a result, the ranged-read path is not a pure file-read operation, and paths containing shell-sensitive characters can be interpreted unexpectedly.
Steps to Reproduce:
- Install ADK.
- Run the minimal reproduction below.
- Call
ReadFileTool with start_line=2 and a path containing a single quote. - Observe that the ranged-read path invokes shell execution instead of direct file I/O.
Expected Behavior:
ReadFileTool should always read file contents directly and should not invoke shell commands based on the requested line range.
Observed Behavior:
When start_line or end_line is provided, ReadFileTool builds and runs a shell command using the provided path.
Environment Details:
- ADK Library Version (pip show google-adk): 1.31.0
- Desktop OS:** Linux
- Python Version (python -V): Python 3.14.4
Model Information:
- Are you using LiteLLM: No
- Which model is being used: N/A
🟡 Optional Information
Regression:
Unknown / N/A
Logs:
text file.
No special logs required. The issue is reproducible from the current
ReadFileTool implementation.
Screenshots / Video:
N/A
Additional Context:
This looks fixable by removing the shell-based ranged-read path and handling line slicing entirely in Python.
Minimal Reproduction Code:
Please provide a code snippet or a link to a Gist/repo that isolates the issue.
importasynciofrompathlibimportPathimporttempfilefromgoogle.adk.environment._local_environmentimportLocalEnvironmentfromgoogle.adk.tools.environment._toolsimportReadFileToolasyncdefmain():
withtempfile.TemporaryDirectory() astd:
env=LocalEnvironment(working_dir=Path(td))
awaitenv.initialize()
target=Path(td) /"sample.txt"target.write_text("line1\nline2\nline3\n", encoding="utf-8")
marker=Path(td) /"marker.txt"# ReadFileTool is expected to treat `path` as data, not as shell syntax.# This payload closes the quoted filename used by the ranged-read path,# injects an extra command, and reopens the quote to keep the shell happy.injected_path=f"sample.txt'; touch {marker}; echo '"tool=ReadFileTool(env)
result=awaittool.run_async(
# `start_line=2` is important because it triggers the code path that# shells out through `cat ... | sed ...` instead of doing pure Python# file I/O.args={"path": injected_path, "start_line": 2},
tool_context=None,
)
print(result)
# If this prints True, a file-read API caused an unintended side effect.# That demonstrates the bug: the path was interpreted by the shell.print("marker exists:", marker.exists())
awaitenv.close()
asyncio.run(main())
How often has this issue occurred?:
ReadFileToolshells out for ranged reads (start_line > 1orend_lineis set) instead of reading the file directly.adk-python/src/google/adk/tools/environment/_tools.py
Lines 190 to 191 in 3e282d2
As a result, the ranged-read path is not a pure file-read operation, and paths containing shell-sensitive characters can be interpreted unexpectedly.
Steps to Reproduce:
ReadFileToolwithstart_line=2and a path containing a single quote.Expected Behavior:
ReadFileToolshould always read file contents directly and should not invoke shell commands based on the requested line range.Observed Behavior:
When
start_lineorend_lineis provided,ReadFileToolbuilds and runs a shell command using the provided path.Environment Details:
Model Information:
🟡 Optional Information
Regression:
Unknown / N/A
Logs:
text file.
Screenshots / Video:
N/A
Additional Context:
This looks fixable by removing the shell-based ranged-read path and handling line slicing entirely in Python.
Minimal Reproduction Code:
Please provide a code snippet or a link to a Gist/repo that isolates the issue.
How often has this issue occurred?: