Skip to content

fix(adk_documentation): confine docs-agent file tools to the managed repos directory#5996

Open
adilburaksen wants to merge 1 commit into
google:mainfrom
adilburaksen:harden/docs-agent-path-confinement
Open

fix(adk_documentation): confine docs-agent file tools to the managed repos directory#5996
adilburaksen wants to merge 1 commit into
google:mainfrom
adilburaksen:harden/docs-agent-path-confinement

Conversation

@adilburaksen
Copy link
Copy Markdown

@adilburaksen adilburaksen commented Jun 6, 2026

The adk_documentation sample tools (read_local_git_repo_file_content, list_directory_contents, search_local_git_repo, and the file-writing step of create_pull_request_from_changes) are invoked by autonomous agents (adk_docs_updater, adk_release_analyzer) that run non-interactively in GitHub Actions and process untrusted input — GitHub issue bodies and release diffs.

read_local_git_repo_file_content only checks os.path.isabs(file_path) before opening the file. Any absolute path is therefore read, including paths outside the repositories cloned under LOCAL_REPOS_DIR_PATH (e.g. the credentials file referenced by GOOGLE_APPLICATION_CREDENTIALS, or /proc/self/environ). list_directory_contents and search_local_git_repo have the same gap, and create_pull_request_from_changes joins caller-provided changes keys onto local_path without rejecting .., so a key like ../../x writes outside the repo.

Because these tools are LLM-driven over untrusted text, a crafted instruction can steer an agent to read a file outside the workspace and place its contents into a pull request it opens. This change keeps the tools within their intended sandbox.

Change

  • Add _resolve_within_repos_dir() that os.path.realpath-resolves a path (collapsing symlinks and ..) and requires the result to stay within LOCAL_REPOS_DIR_PATH.
  • Apply it to read_local_git_repo_file_content, list_directory_contents, and search_local_git_repo.
  • In create_pull_request_from_changes, resolve each target path and reject any that escapes the repository root before writing.
  • Add tools_test.py covering: reads/lists/searches outside the sandbox are denied, in-sandbox reads still succeed, a symlink pointing outside is denied, and a traversing changes key is rejected without writing.

No functional change for legitimate in-repo paths.

Testing Plan

Added contributing/samples/adk_team/adk_documentation/tools_test.py (unittest), covering both the rejection and the no-regression paths:

  • test_read_outside_sandbox_is_deniedread_local_git_repo_file_content rejects /etc/passwd and /proc/self/environ.
  • test_read_symlink_escape_is_denied — a symlink inside the sandbox pointing to /etc/passwd is rejected (paths are realpath-resolved before the check).
  • test_read_inside_sandbox_succeeds — a legitimate file under LOCAL_REPOS_DIR_PATH is still read successfully (no regression).
  • test_list_and_search_outside_sandbox_are_deniedlist_directory_contents and search_local_git_repo reject paths outside the sandbox.
  • test_create_pr_rejects_path_traversal_in_changes — a changes key such as ../../../../tmp/evil.txt is rejected and no file is written outside the repo.

Test logs

$ PYTHONPATH=contributing/samples/adk_team python -m pytest \
    contributing/samples/adk_team/adk_documentation/tools_test.py -v

tools_test.py::PathConfinementTest::test_create_pr_rejects_path_traversal_in_changes PASSED [ 20%]
tools_test.py::PathConfinementTest::test_list_and_search_outside_sandbox_are_denied PASSED [ 40%]
tools_test.py::PathConfinementTest::test_read_inside_sandbox_succeeds            PASSED [ 60%]
tools_test.py::PathConfinementTest::test_read_outside_sandbox_is_denied          PASSED [ 80%]
tools_test.py::PathConfinementTest::test_read_symlink_escape_is_denied           PASSED [100%]

======================== 5 passed in 0.27s =========================

…repos directory

The adk_documentation tools are driven by autonomous agents (adk_docs_updater,
adk_release_analyzer) that run non-interactively in CI and process untrusted
input (issue bodies, release diffs). read_local_git_repo_file_content only
checked os.path.isabs(), so any absolute path was read, including files outside
the cloned repos under LOCAL_REPOS_DIR_PATH (e.g. $GOOGLE_APPLICATION_CREDENTIALS,
/proc/self/environ). list_directory_contents and search_local_git_repo had the
same gap, and create_pull_request_from_changes joined caller-provided change keys
onto local_path without rejecting '..'.

Add _resolve_within_repos_dir() (realpath-resolve + confine to LOCAL_REPOS_DIR_PATH),
apply it to the read/list/search tools, and reject change paths that escape the
repository root before writing. Add tools_test.py covering these cases. No change
for legitimate in-repo paths.
@adk-bot adk-bot added the tools [Component] This issue is related to tools label Jun 6, 2026
@adk-bot
Copy link
Copy Markdown
Collaborator

adk-bot commented Jun 6, 2026

Response from ADK Triaging Agent

Hello @adilburaksen, thank you for creating this PR! It is a great security improvement for our document-agent tools.

To make it easier for our reviewers to review and merge your PR, please ensure it aligns with our Contribution Guidelines. Specifically, we kindly ask you to update the PR description to include the following:

  1. A Testing Plan section: Please include a dedicated testing plan section describing how you tested these changes (e.g. the scenarios covered by the new tools_test.py).
  2. Logs or Screenshots: Since this is a bug/security fix, please provide the console outputs or pytest logs demonstrating that the tests (especially the newly added unit tests) are passing.

Providing this context will save review time and help us process your contribution faster. Thank you!

@adilburaksen
Copy link
Copy Markdown
Author

Thanks! I've updated the PR description with a Testing Plan section (the five scenarios covered by the new tools_test.py) and the pytest logs showing all 5 tests passing. Let me know if anything else is needed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

tools [Component] This issue is related to tools

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants