Lesson 9 / 25
Path Guards and Symlinks
Keep file tools inside the project folder and defeat ../ and symlink escapes.
Resolve before you compare
A file tool should only touch paths inside the project. Checking that the path string starts with the project folder is not enough: ../outside.txt and a symlink inside the project that points elsewhere both escape. The fix is to resolve the real path first (collapsing .. and following links) and then check it is inside the root.
A room with walls
Put the agent in a space where even a bad command can only reach what you chose to expose.
Naive vs resolved, run
I ran this in a temporary directory with a real symlink. The naive string check allows all three paths; the resolved check blocks both the .. path and the symlink.
import os, tempfile
from pathlib import Path
root = Path(tempfile.mkdtemp()).resolve()
work = root / "work"; work.mkdir()
(root / "outside.txt").write_text("secret")
(work / "ok.txt").write_text("hi")
os.symlink(root / "outside.txt", work / "link.txt")
def naive_path(p): return str(work / p).startswith(str(work))
def safe_path(p):
r = (work / p).resolve()
return r == work or work in r.parents
for p in ("ok.txt", "../outside.txt", "link.txt"):
print(p, "naive:", naive_path(p), "safe:", safe_path(p))
Output:
ok.txt naive: True safe: True ../outside.txt naive: True safe: False link.txt naive: True safe: False
Check at use time
A path can change between check and use (a "time-of-check to time-of-use" race), for example a file swapped for a symlink. Do the check as close to the file operation as possible, and rely on the sandbox as the real boundary.
Quick check: Why is "path string starts with project folder" an unsafe check?
- Folders cannot have names
- Strings cannot be compared
- `..` and symlinks can point outside the folder
- It is too slow
Answer
`..` and symlinks can point outside the folder — Only the resolved real path shows where a file operation will actually land.