Skip to content

Conversation

@venkateshpabbati
Copy link
Owner

Potential fix for https://github.com/venkateshpabbati/adk-python/security/code-scanning/27

To safely handle user-provided values like app_name in filesystem paths, normalization and prefix validation should be performed.

Best-Fix Approach:
Normalize all user-derived paths after joining with the intended safe base directory (base_path) using os.path.normpath. Then enforce that the normalized path starts with the base directory, preventing path traversal and absolute path exploits. Optionally, reject app names containing suspicious components (such as path separators, '.', '..', etc.). For defense-in-depth, werkzeug.utils.secure_filename could further sanitize filenames but for directories that may span multiple segments, normalization and prefix-checking suffices.

Required change locations:

  • The construction of agent_dir and destination_dir in builder_cancel
  • Any other place where untrusted app_name/values are used to construct filesystem paths

Change summary:

  • After joining base_path and app_name, normalize the resulting path and check that it remains inside base_path.
  • Apply this check before using the path in further directory/file operations.
  • Add a helper to handle this in a single place for readability.
  • Raise an exception or return an error if the check fails.

Imports needed: None beyond standard libraries (os, Path, etc.).

Suggested fixes powered by Copilot Autofix. Review carefully before merging.

…in path expression

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
source_dir = agent_dir
source_items = set(os.listdir(source_dir))
try:
source_items = set(os.listdir(source_dir))

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression High

This path depends on a
user-provided value
.

Copilot Autofix

AI 18 days ago

General Fix:
Normalize and resolve all user-controlled paths before use and verify, using a more robust mechanism than a simple string "startswith" check. Also, when using the path as a directory to be listed or accessed, use the authoritative resolved path object or string.

Detailed steps:

  • Use Path.resolve(strict=False) on both agent_dir and base_path to get their true, canonical forms.
  • Instead of str(agent_dir).startswith(str(base_path)), use base_path in agent_dir.parents or agent_dir == base_path to check containment.
  • Do the same for destination_dir.
  • When assigning source_dir, use the resolved, verified path for safety.
  • Update/add imports as needed for Path.

Lines to change:

  • Lines assigning and checking agent_dir, destination_dir, and source_dir (lines 251-263).
  • Strengthen the check and use the correct resolved path objects.

Suggested changeset 1
src/google/adk/cli/fast_api.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/google/adk/cli/fast_api.py b/src/google/adk/cli/fast_api.py
--- a/src/google/adk/cli/fast_api.py
+++ b/src/google/adk/cli/fast_api.py
@@ -248,16 +248,16 @@
   @working_in_progress("builder_save is not ready for use.")
   @app.post("/builder/app/{app_name}/cancel", response_model_exclude_none=True)
   async def builder_cancel(app_name: str) -> bool:
-    base_path = Path.cwd() / agents_dir
-    # Safely compose agent_dir
-    agent_dir = os.path.normpath(os.path.join(base_path, app_name))
+    base_path = (Path.cwd() / agents_dir).resolve()
+    # Safely compose agent_dir, then resolve
+    agent_dir = (base_path / app_name).resolve()
     # Ensure agent_dir is within base_path
-    if not str(agent_dir).startswith(str(base_path)):
+    if base_path not in agent_dir.parents and agent_dir != base_path:
       logger.error(f"Attempted path traversal attack with agent_dir: {agent_dir}")
       return False
-    destination_dir = os.path.normpath(os.path.join(agent_dir, "tmp/" + app_name))
+    destination_dir = (agent_dir / ("tmp/" + app_name)).resolve()
     # Ensure destination_dir is also within base_path
-    if not str(destination_dir).startswith(str(base_path)):
+    if base_path not in destination_dir.parents and destination_dir != base_path:
       logger.error(f"Attempted path traversal attack with destination_dir: {destination_dir}")
       return False
     source_dir = agent_dir
EOF
@@ -248,16 +248,16 @@
@working_in_progress("builder_save is not ready for use.")
@app.post("/builder/app/{app_name}/cancel", response_model_exclude_none=True)
async def builder_cancel(app_name: str) -> bool:
base_path = Path.cwd() / agents_dir
# Safely compose agent_dir
agent_dir = os.path.normpath(os.path.join(base_path, app_name))
base_path = (Path.cwd() / agents_dir).resolve()
# Safely compose agent_dir, then resolve
agent_dir = (base_path / app_name).resolve()
# Ensure agent_dir is within base_path
if not str(agent_dir).startswith(str(base_path)):
if base_path not in agent_dir.parents and agent_dir != base_path:
logger.error(f"Attempted path traversal attack with agent_dir: {agent_dir}")
return False
destination_dir = os.path.normpath(os.path.join(agent_dir, "tmp/" + app_name))
destination_dir = (agent_dir / ("tmp/" + app_name)).resolve()
# Ensure destination_dir is also within base_path
if not str(destination_dir).startswith(str(base_path)):
if base_path not in destination_dir.parents and destination_dir != base_path:
logger.error(f"Attempted path traversal attack with destination_dir: {destination_dir}")
return False
source_dir = agent_dir
Copilot is powered by AI and may make mistakes. Always verify output.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants