Skip to main content
Back to all posts
6 minAgentic AI SecurityJuly 19, 2026

Resolve the Real Path Before an AI Coding Agent Writes the File

AI coding agents need real-path write gates that bind approval to the canonical target and block traversal, symlink escapes, and path-swap races.

RM

Ryan Macomber

Editor, VibeSec Advisory

An AI coding agent should not get write access because a path string looks like it belongs to the repository.

Short answer: Before an AI coding agent writes, edits, deletes, or renames a file, resolve the requested path against the canonical repository root. Show the requested and resolved targets. Authorize the resolved object, not the string. Then run the mutation through a rooted filesystem API, descriptor boundary, or sandbox.

Resolve the target first. Check the resolved target against the resolved repository root. Bind any approval to that exact target. Then perform the write through a narrow filesystem boundary.

That sounds fussy until a repository contains a symlink.

The agent asks to edit docs/config.json. The path starts inside the repo. The approval prompt shows a harmless project file. But docs/config.json can point somewhere else. A parent directory can be swapped after the check. A naive string-prefix test can also mistake /tmp/repo2 for a child of /tmp/repo.

The model does not need malicious intent for any of this. It only needs a file tool that authorizes a name instead of the object behind the name.

File names are not stable security boundaries

MITRE CWE-22 covers path traversal through relative components, separators, and absolute paths. CWE-180 captures the ordering mistake: validate first, canonicalize later. A path can pass the first check and resolve outside the approved directory afterward.

Symlinks add another layer. CWE-61 describes how following a symbolic link can move a file operation outside its intended control boundary. This is not theoretical for coding agents. A Zed security advisory documents agent file tools that checked logical worktree paths but did not resolve symlinks, which allowed access outside the project. A LangChain advisory describes related confinement failures where path checks did not consistently hold after resolution.

The fix begins with a simple rule: authorize the canonical target, not the path string supplied by the model.

Use a four-part write gate

I would put four steps in front of every agent file mutation.

1. Resolve

Start with a canonical repository root. Decode and normalize the requested path. Resolve symlinks and relative components where the platform permits it.

Lexical cleanup helps, but it is not enough. Node's path.resolve normalizes a string. Node's fs.realpath asks the filesystem for the actual location. Python makes the same distinction through Path.resolve and os.path.realpath. Rust exposes std::fs::canonicalize.

Reject absolute pivots, .. escapes, broken links with ambiguous create behavior, and targets that land outside the approved roots.

2. Bind

Keep reading with free field-guide resources.

VibeSec Advisory publishes practical research, Skills, workflow examples, MCP notes, prompt injection tests, and AI red-team lessons for builders working with agentic AI.

Create an approval record for the resolved target.

The record should name the requested path, canonical root, canonical target, operation, and expected content or diff. If the requested and resolved paths differ, show both. If a symlink, mount, or parent directory changes before execution, invalidate the approval.

This matters because a human can only approve what the interface actually shows. Approval for repo/docs/config.json is not approval for whichever file that name points to later.

3. Authorize

Containment checks must understand path boundaries.

A raw startsWith check is a common footgun. /tmp/repo2/file starts with /tmp/repo, but it is not inside that repository. Compare canonical path segments or use a platform API that works relative to an already-open root.

The policy should also protect high-risk paths inside the repository. A repo-wide write grant does not mean an agent should silently modify .git/config, agent rules, sandbox policy, hooks, or credential files.

4. Operate

Perform the mutation through the narrowest available primitive.

On Linux, openat2 can apply resolution rules inside the kernel. RESOLVE_BENEATH and RESOLVE_IN_ROOT constrain lookup to a directory. RESOLVE_NO_SYMLINKS can reject symlink components. Go's os.Root offers a higher-level rooted filesystem API and explicitly warns that resolving a symlink and opening it later can still leave a race.

That last point is easy to miss.

Canonicalization is necessary, but canonicalize-then-open is still a check followed by a use. If another process can change the filesystem between those steps, the target can move.

CWE-367 and SEI CERT FIO01-C recommend object-bound operations where possible. File descriptors are tied more closely to the opened object than repeated path lookups. A rooted descriptor API, no-follow policy, post-open identity check, and atomic replace inside a trusted directory are stronger than another prompt telling the model to be careful.

Sandboxes still matter

A safe file wrapper should not be the only control.

Official agent tooling docs make the same separation. Codex approvals and security distinguishes the OS-enforced sandbox from the approval policy. Claude Code sandboxing similarly describes OS-level filesystem restrictions for shell commands and child processes.

The wrapper decides whether this file operation is allowed. The sandbox limits what happens if the wrapper, tool, model, or approval flow is wrong.

Containers do not remove the need to review mounts. Docker's bind-mount documentation notes that bind mounts can write to host files by default. A coding agent inside a container with a broad home-directory mount still has a broad host boundary.

Test the escapes before trusting the gate

Do not validate this control with one normal file edit. Seed the cases that break weak wrappers.

  • Try ../outside.txt and an absolute path.
  • Try a sibling-prefix path such as /tmp/repo2 against /tmp/repo.
  • Put a symlink inside the repo that points to a credential file outside it.
  • Swap a checked parent directory for a symlink immediately before the write.
  • Approve one target, then change the link before execution.
  • Run a child process that attempts an out-of-workspace write.
  • Try to edit the policy and hook files that enforce the boundary.
  • Repeat the tests inside a dev container with realistic bind mounts.

The expected result is boring. The tool denies the operation, explains which resolved boundary failed, and leaves a replayable record.

That is the companion check missing from many vibe-coding security lists. "Only write inside the repository" is a policy sentence. A real-path write gate turns it into an enforceable filesystem decision.

The VibeSec Advisory vibe-coding security checklist covers the wider application review. Add this gate anywhere an AI coding agent can create, edit, rename, extract, or delete files.

Sources

Next free step: Test your agent. Seed the symlink, sibling-prefix, path-swap, and container bind-mount cases above before giving the file tool write authority.

AI Workflows Weekly

Read the archive

Practical notes on governed AI workflows, guardrails, and safer automation. No spam, unsubscribe anytime.

First-party signup with double opt-in. No embedded newsletter iframe, no analytics cookies, and unsubscribe anytime.

Keep testing agentic AI risk.

VibeSec Advisory is a free field guide. Use the research archive, Skill Library, and workflow examples to keep improving what you are building.