Skip to content

fix(utils): fix sibling-directory escape in materializeFiles path check - #603

Open
herdiyana256 wants to merge 1 commit into
google:mainfrom
herdiyana256:fix/materialize-files-sibling-dir-traversal
Open

fix(utils): fix sibling-directory escape in materializeFiles path check#603
herdiyana256 wants to merge 1 commit into
google:mainfrom
herdiyana256:fix/materialize-files-sibling-dir-traversal

Conversation

@herdiyana256

Copy link
Copy Markdown
Contributor

materializeFiles (core/src/utils/file_utils.ts) guards its target directory with fullPath.startsWith(resolvedBaseDir) — a path-separator-unaware prefix match. A relative file name like ../<dir>-evil/x resolves to a sibling of the target directory but still satisfies that check, since resolvedBaseDir and the sibling path share the same string prefix with no separator required between them. Because the resulting directory is created with fs.mkdir(path.dirname(finalPath), {recursive: true}), the sibling directory does not need to already exist.

materializeFiles is called with no dir argument (defaulting to process.cwd()) from run_skill_script_tool.ts and run_skill_inline_script_tool.ts on result.outputFiles, which come back from whatever CodeExecutor is configured — including AgentEngineSandboxCodeExecutor, which decodes each output file's name directly from the remote sandbox execution result's metadata (attributes['file_name'], base64-decoded, unvalidated). So a file name chosen by code running inside that isolated sandbox can, via this gap, land outside the directory materializeFiles was scoped to on the orchestrator host.

Confirmed by executing the real function: with target dir /tmp/x/sandboxdir, an output file named ../sandboxdir-pwned/shell.js was written to /tmp/x/sandboxdir-pwned/shell.js, outside the intended directory.

This replaces the check with a helper requiring a path-separator boundary (or exact equality) — the same containment pattern already used by FileArtifactService.assertInsideRoot (added in #210 for a related but distinct traversal in that file). Adds a regression test for exactly this gap: the existing ../escape.txt-style test uses fs.mkdtemp's randomly-suffixed directory name, so it never happens to collide with a real sibling name and does not exercise this path.

materializeFiles guarded against escaping its target directory with
`fullPath.startsWith(resolvedBaseDir)`, a path-separator-unaware prefix match:
a relative name like `../<dir>-evil/x` resolves outside the target directory
but still starts with the same string, so it passed the check. Since the
resulting directories are created with `fs.mkdir(..., {recursive: true})`, this
let a caller-supplied file name write outside the intended directory as long as
its resolved sibling path happened to share the base directory's name as a
prefix.

materializeFiles is reachable from run_skill_script_tool /
run_skill_inline_script_tool with output files from a configured
CodeExecutor, including AgentEngineSandboxCodeExecutor, which decodes the
file name from the remote sandbox's own execution-result metadata; a caller
with no other privileges beyond choosing that file name could reach a
directory outside the one materializeFiles was scoped to.

Require a path-separator boundary (or exact equality) instead of a bare
prefix match, matching the containment check already used in
FileArtifactService's assertInsideRoot. Add a regression test for the specific
gap: an escape into a sibling directory that shares a name prefix with the
target directory, which the existing `../escape.txt`-style test does not
exercise (mkdtemp's random suffix means that case never collides with a real
sibling name).
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.

1 participant