You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
agent-computer/src/workspace.ts describes path confinement as three layers, and names the third, resolving symlinks, as the one people miss. On the write path it stops one component short.
resolvePath(requested, forWrite: true) resolves the directory a write will land in and checks that against the root, then returns the lexical target. If the last component is itself a symlink, writeFile follows it, and the bytes land wherever it points, as root inside the container.
Reproduced against a temporary workspace on main2b2bc39:
workspace/notes.md -> /outside/victim.txt (victim.txt contains "original")
write("notes.md", "OWNED") -> no refusal, victim.txt now contains "OWNED"
The read path is not affected: it resolves the file itself and refuses. Symlinked directories are refused in both directions, and there is an existing test for that case. It is only the final component of a write.
A dangling link is the same escape with an extra step. Writing through draft.txt -> /outside/not-yet.txt creates the file it names, so a check that asks where the link points learns nothing and lets it past.
Reachability
Nothing in the workspace API creates a symlink, which is why this reads as covered. The links come from elsewhere:
the shell added in Run OpenBot as one container, and give a Bot a shell #62. ln -s /etc/crontab /workspace/notes.md is one command, and a deployment that denies computer_run_command by policy while allowing computer_write_file is exactly the configuration where this matters.
an archive a Bot downloaded and unpacked.
a /workspace volume left by an earlier deployment.
in the shared-computer arrangement (no COMPUTER_SUPERVISOR_URL), every Bot shares one container and one workspace, so one Bot plants the link and another writes through it.
Test plan
Put a symlink at notes.md in a Bot's workspace pointing outside, ask the Bot to write to notes.md. Expect a refusal and the target file unchanged.
Same with a link pointing at a file that does not exist yet. Expect a refusal and no file created.
A link pointing back inside the workspace still reads and writes normally.
agent-computer/src/workspace.tsdescribes path confinement as three layers, and names the third, resolving symlinks, as the one people miss. On the write path it stops one component short.resolvePath(requested, forWrite: true)resolves the directory a write will land in and checks that against the root, then returns the lexical target. If the last component is itself a symlink,writeFilefollows it, and the bytes land wherever it points, as root inside the container.Reproduced against a temporary workspace on
main2b2bc39:The read path is not affected: it resolves the file itself and refuses. Symlinked directories are refused in both directions, and there is an existing test for that case. It is only the final component of a write.
A dangling link is the same escape with an extra step. Writing through
draft.txt -> /outside/not-yet.txtcreates the file it names, so a check that asks where the link points learns nothing and lets it past.Reachability
Nothing in the workspace API creates a symlink, which is why this reads as covered. The links come from elsewhere:
ln -s /etc/crontab /workspace/notes.mdis one command, and a deployment that deniescomputer_run_commandby policy while allowingcomputer_write_fileis exactly the configuration where this matters./workspacevolume left by an earlier deployment.COMPUTER_SUPERVISOR_URL), every Bot shares one container and one workspace, so one Bot plants the link and another writes through it.Test plan
notes.mdin a Bot's workspace pointing outside, ask the Bot to write tonotes.md. Expect a refusal and the target file unchanged.A PR follows.