# Agent file tool path escape

Agent file tools joining user paths lexically, without canonicalization or symlink checks, letting reads/writes escape the workspace

_Area: AI and LLM app security. Seen in 2 disclosed reports; latest 2026-10-13. Updated 2026-10-25._

## How it works
An agent's file tool receives a path from the model, which an attacker can steer via prompt injection. The tool assumes a lexical check (rejecting `..`, absolute prefixes, or comparing raw filename strings) means the path stays inside the workspace or avoids protected files. That assumption breaks because the check and the actual filesystem operation resolve the path differently: symlinks are followed at open time, and the OS canonicalizes names (trailing dots/spaces, `\\?\` prefixes) after the string comparison. The guard inspects one string; the kernel acts on another.

## Where it shows up
- File read/write sites using a bare `workspace.join(userPath)` instead of the codebase's central path resolver [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-w7wx-5q49-r59w.md)
- Lexical component checks (Prefix/RootDir/ParentDir) presented as containment [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-w7wx-5q49-r59w.md)
- Protected-filename or allowlist checks comparing raw strings before a write [E1](https://bugbountylibrary.com/r/blog%3Aembracethered-com-blog-posts-2026-visual-studio-copilot-remote-code-execution.md)
- Tool parameters passed to `fs.readFile`/write with only `.trim()` and no canonicalization or directory restriction [E3](https://bugbountylibrary.com/r/ghsa%3AGHSA-jq4x-98m3-ggq6.md)
- Agent tools that can write config files (`.mcp.json`, settings) that are later loaded and executed [E1](https://bugbountylibrary.com/r/blog%3Aembracethered-com-blog-posts-2026-visual-studio-copilot-remote-code-execution.md)

## How to review
Find every file-read and file-write site reachable from a tool parameter. The invariant: the path actually opened must be canonicalized (symlinks resolved, OS name normalization applied) and then verified to lie under the intended root, using the same resolved form the OS will use. A string comparison or lexical component check alone does not satisfy this. Check that all sites route through one resolver; grep for bypasses of it [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-w7wx-5q49-r59w.md). For protected files, compare canonicalized names, not raw input [E1](https://bugbountylibrary.com/r/blog%3Aembracethered-com-blog-posts-2026-visual-studio-copilot-remote-code-execution.md).

## Variants seen
- Trailing-dot filename bypass of a protected-name check, enabling config write and RCE [E1](https://bugbountylibrary.com/r/blog%3Aembracethered-com-blog-posts-2026-visual-studio-copilot-remote-code-execution.md)
- Workspace symlink followed at read time, leaking outside-workspace bytes to a vision endpoint [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-w7wx-5q49-r59w.md)
- Raw path straight into `fs.readFile`, arbitrary file read forwarded to a client [E3](https://bugbountylibrary.com/r/ghsa%3AGHSA-jq4x-98m3-ggq6.md)

## Why fixes fail
A prior Windows fix tightened the lexical guard but left the site bypassing the central resolver, so symlink escape remained [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-w7wx-5q49-r59w.md).

## Impact and severity
High: remote code execution via agent-written config [E1](https://bugbountylibrary.com/r/blog%3Aembracethered-com-blog-posts-2026-visual-studio-copilot-remote-code-execution.md), and disclosure of arbitrary or out-of-workspace file contents [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-w7wx-5q49-r59w.md)[E3](https://bugbountylibrary.com/r/ghsa%3AGHSA-jq4x-98m3-ggq6.md). Worse when the agent holds a gateway token or the leaked data is forwarded to an external endpoint.

## Evidence
- E1: https://bugbountylibrary.com/r/blog%3Aembracethered-com-blog-posts-2026-visual-studio-copilot-remote-code-execution.md
- E2: https://bugbountylibrary.com/r/ghsa%3AGHSA-w7wx-5q49-r59w.md
- E3: https://bugbountylibrary.com/r/ghsa%3AGHSA-jq4x-98m3-ggq6.md
