Skip to content

< all problems54 · Level 04, Tool Calling

Keep the File Tools Inside the Workspace

easy · implement · Tool Calling

A coding agent's read_file tool takes whatever path the model asks for. Yesterday the model asked for ../../.aws/credentials, and got them.

The model did not go looking for trouble. A README said "config lives two directories up", the model believed it, and a tool with no notion of a boundary did the rest. Every file tool needs one rule, enforced in code rather than in the prompt: a path resolves inside the workspace, or the tool refuses.

Implement safe_join(root, path). root is the workspace directory as a POSIX path (/repo). Return the resolved path as a string, or raise PathEscape (provided) when the request would leave the workspace.

Resolve the way a filesystem would, without touching one:

  • Split on /. Treat a backslash as a separator too; the model will produce Windows paths on a Linux box.
  • Drop empty segments and ., so src//./app.py is src/app.py.
  • .. steps up one segment. Stepping up from the workspace root is an escape.
  • An absolute path (/etc/passwd) or a drive letter (C:) is an escape, even when it happens to start with root. The model should not be handing you absolute paths at all.
  • A leading ~ is an escape: it means the home directory, which is not the workspace.
  • A null byte anywhere is an escape. It truncates the path in C and is never legitimate.

The result is root followed by the surviving segments. An empty request, or ., resolves to root itself.

read_file(files, path) in the fixtures calls your function before it looks anything up. That is the whole pattern: the check sits between the model and the filesystem, and it does not care how persuasive the README was.