Skip to content

fix(configurable): prevent path traversal in AgentTool config_path resolution - #878

Merged
baptmont merged 6 commits into
google:mainfrom
adilburaksen:fix/config-path-traversal
Aug 7, 2026
Merged

fix(configurable): prevent path traversal in AgentTool config_path resolution#878
baptmont merged 6 commits into
google:mainfrom
adilburaksen:fix/config-path-traversal

Conversation

@adilburaksen

Copy link
Copy Markdown
Contributor

Summary

ResolveAgentReference in configurable_utils.go accepted absolute refPath values unconditionally and joined relative paths via filepath.Join without boundary enforcement. An attacker-controlled config_path field in an agent YAML could read arbitrary files accessible to the server process.

Vulnerable pattern (before):

if !filepath.IsAbs(refPath) {
    targetPath = filepath.Join(filepath.Dir(parentPath), refPath)  // no ".." check
}
absPath, _ := filepath.Abs(targetPath)
a, err := FromConfig(ctx, absPath)  // reads file unconditionally

Fix

  • Reject absolute refPath values
  • After resolving, verify the path stays within the parent agent's directory using strings.HasPrefix on the cleaned path

Related

Same vulnerability exists in adk-python (PR: google/adk-python#5826) and adk-java (PR: google/adk-java#1218) — fix pattern is identical across all three SDKs.

@google-cla

google-cla Bot commented May 23, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

…solution

Absolute config_path values were accepted unconditionally, and relative
paths were joined without boundary validation, allowing traversal outside
the agent directory via "../../../etc/passwd" style inputs.

Fix: reject absolute paths; verify the resolved path stays within the
parent agent's directory using strings.HasPrefix after filepath.Clean.
adilburaksen and others added 3 commits June 6, 2026 15:27
…nfig_path containment check

The containment check for AgentTool config_path compared an absolute target
path against a parent directory that was only cleaned, not made absolute, so a
relative parentPath caused legitimate in-directory references to be rejected.
The comparison was also purely lexical, so a symlink inside the agent directory
could still resolve outside it and be loaded.

Make both sides absolute before comparing and resolve symlinks where the paths
exist, falling back to the lexical path when they do not so that a missing file
still reports as not found rather than as a traversal.

Add regression tests covering absolute paths, parent traversal, symlink escape,
references inside the agent directory, and a relative parent path.
@adilburaksen

Copy link
Copy Markdown
Contributor Author

@wolo-lab @hanorik — could one of you take a look at this when you have a moment, or route it to whoever owns internal/configurable?

This closes a path traversal in ResolveAgentReference: an attacker-supplied config_path is currently used verbatim when absolute, and joined without a containment check when relative, so a sub-agent reference such as ../../../../etc/passwd is read from outside the parent agent's directory.

The same issue was fixed in the sibling implementations and both have landed:

adk-go is the last of the three still carrying the original pattern.

I have just pushed an update that tightens the check and adds tests:

  • both sides of the comparison are now made absolute. The previous revision compared an absolute target against a parent directory that was only filepath.Cleaned, so a relative parentPath rejected legitimate in-directory references.
  • symlinks are resolved with filepath.EvalSymlinks where the paths exist, so a symlink inside the agent directory can no longer resolve outside it. When a path does not exist the check falls back to the lexical path, so a missing file still reports as not found rather than as a traversal.
  • added configurable_utils_test.go covering absolute paths, parent traversal, symlink escape, a legitimate in-directory reference, and a relative parent path. The symlink and relative-path cases fail against the previous revision of this PR and pass with the update.

Status: CLA signed, check-changes green, go build, go vet, gofmt and the package tests all clean locally, and the branch is rebased on current main — it still applies to internal/configurable/configurable_utils.go after the V2 restructure. No reviewer was ever assigned to the PR, which I suspect is why it has been sitting rather than any deliberate decision.

Happy to adjust the approach to whatever you prefer, or feel free to take the change over and land it yourselves.

@wolo-lab

wolo-lab commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Thank you for preparing a fix. @baptmont, please take a look at this PR.

@wolo-lab
wolo-lab requested a review from baptmont August 6, 2026 12:31
@baptmont
baptmont merged commit 604dd63 into google:main Aug 7, 2026
13 checks passed
wolo-lab added a commit that referenced this pull request Aug 10, 2026
…solution (#878) (#1277)

fix(configurable): prevent path traversal in AgentTool config_path resolution

Reject absolute config_path values and verify that a relative one resolves
inside the referencing agent's directory, resolving symlinks where the paths
exist. Matches the hard rejection adk-python landed in 171ae9e; adk-java
(#1218) chose a warn-only deprecation instead.

BREAKING: an absolute config_path is no longer accepted.

---------

* fix(configurable): prevent path traversal in AgentTool config_path resolution

Absolute config_path values were accepted unconditionally, and relative
paths were joined without boundary validation, allowing traversal outside
the agent directory via "../../../etc/passwd" style inputs.

Fix: reject absolute paths; verify the resolved path stays within the
parent agent's directory using strings.HasPrefix after filepath.Clean.

* fix(configurable): compare absolute, symlink-resolved paths in the config_path containment check

The containment check for AgentTool config_path compared an absolute target
path against a parent directory that was only cleaned, not made absolute, so a
relative parentPath caused legitimate in-directory references to be rejected.
The comparison was also purely lexical, so a symlink inside the agent directory
could still resolve outside it and be loaded.

Make both sides absolute before comparing and resolve symlinks where the paths
exist, falling back to the lexical path when they do not so that a missing file
still reports as not found rather than as a traversal.

Add regression tests covering absolute paths, parent traversal, symlink escape,
references inside the agent directory, and a relative parent path.

---------

Co-authored-by: João Westerberg <westerberg@google.com>

(cherry picked from commit 604dd63)

Co-authored-by: Adil Burak Şen <56400880+adilburaksen@users.noreply.github.com>
jjsasha63 added a commit that referenced this pull request Aug 12, 2026
…erences

#878 added a containment check to ResolveAgentReference, but the workflow
loader resolves node references on a separate path that never reaches it.
resolveNodeLike accepted an absolute reference and joined a relative one
without validating the result, so a workflow config could name any .yaml
or .yml file on disk.

The FunctionNode, JoinNode and ToolNode branches of resolveNodeFromYAML all
return before its fall-through call to ResolveAgentReference, so for those
node classes an out-of-tree file was read, parsed and instantiated with no
containment check at all. Only the agent branch was covered, and by the time
it ran the read had already happened.

Extract the check into resolveContainedConfigPath and call it from both
resolvers, before the file is read and before the node cache is consulted.
One shared copy is the point: two independent resolvers is how the gap
appeared in the first place.

BREAKING: an absolute workflow node reference is no longer accepted, matching
the config_path behaviour #878 established.
tonydzi added a commit to tonydzi/adk-go that referenced this pull request Aug 21, 2026
PR google#878 added a containment check to ResolveAgentReference: an AgentTool
config_path may not be absolute, and a relative one must resolve inside the
referencing agent's directory. Workflow edge references were left on the
pre-google#878 code — absolute refs accepted unconditionally, relative ones joined
with no boundary check — and resolveNodeFromYAML reads them straight off disk
with os.ReadFile before dispatching on agent_class. A workflow config could
therefore load and instantiate a FunctionNode, JoinNode or ToolNode from
anywhere on the filesystem.

Extract the containment logic into resolveConfigReference and route both
ResolveAgentReference and resolveNodeLike through it, so the rule has one
implementation rather than one per reference kind.

Two fixes to the check itself, both found while covering the shared helper:

  - The parent directory is now resolved before the reference is joined onto
    it, so both sides of the comparison are rooted in the same real path.
    Resolving only the parent side meant that a parent directory reached
    through a symlink plus a not-yet-existing target — which has no symlinks
    to resolve and so keeps its unresolved spelling — was reported as a
    traversal instead of as not found, the case google#878 intended to allow.

  - A reference carrying a volume name is rejected alongside absolute ones.
    On Windows a drive-relative reference such as `C:node.yaml` is not
    absolute yet still escapes, by resolving against the current directory of
    that drive; filepath.VolumeName also covers UNC paths and is empty on
    Unix.

BREAKING: an absolute config_path is no longer accepted in workflow edge
references, matching the google#878 change to agent references.

Assisted-by: Claude Opus 5 <noreply@anthropic.com>
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.

3 participants