Keep an agent's file writes inside its workspace

Agent buildMedium30 min

Your task

Your coding agent's file tool may only write inside its workspace. The paths come from the model, so they can contain .., and the repo can contain symbolic links (a link is a path that points somewhere else). The guard has to resolve each path the way the file system would before it allows the write.

Write check_writes(root, links, paths) that returns "allow" or "deny" for each path.

  • root: the workspace, an absolute path such as /work/app. It is never /, has no empty, . or .. parts, does not end in /, and none of its own parts is a link.
  • links: a dict from a link's path to its target. Each key is an absolute path with no empty, . or .. parts. A target is absolute when it starts with /, and otherwise relative to the directory that holds the link. Targets may contain ., .., empty parts and other links.
  • paths: the paths the agent wants to write to. A path that starts with / is absolute. Any other path is relative to root.
  • Return a list with one decision per path, in order.

Rules:

  1. Resolve a path one part at a time, splitting on /. Skip empty parts and .. A .. moves to the parent of the current directory. At / it stays at /.
  2. After each step into a part, if the path reached so far is a key in links, replace that part with the link's target. Resolve the target's parts from / if it is absolute, or from the directory that holds the link otherwise, then go on with the rest of the path. This applies to the last part too.
  3. Each replacement counts as one hop. A path that needs more than 40 hops is denied: it is caught in a loop of links.
  4. Allow the write when the resolved path is root, or starts with root followed by /. Deny it otherwise.

Examples

check_writes(root: str, links: dict[str, str], paths: list[str]) -> list[str]
Example 1
Inputcheck_writes("/work/app", {}, ["src/main.py", "../secrets.txt", "/work/app/./notes/../README.md"])
Output["allow", "deny", "allow"]
Example 2
Inputcheck_writes("/work/app", {"/work/app/cache": "/tmp/cache"}, ["cache/x.bin", "build/out.js"])
Output["deny", "allow"]
Example 3
Inputcheck_writes("/work/app", {}, ["/work/app2/x", "/work/app"])
Output["deny", "allow"]