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
{{ message }}
Repository navigation
[Bug] The desktop read / write / edit tools resolve their target lexically, so a symlink inside the workspace reaches outside it #526
resolveLocalCandidatePath → normalizeLocalExistingPath resolves the
target against the workspace root and hands that spelling to pi, which opens
it as-is. The path guard that already knows about symlinks
(packages/agent-tools/src/desktop/path-guard.ts) is not called by these
three tools; it serves local-website-deploy.ts and the cloud file-transfer.ts.
Reproduction
In the workspace root, create link -> /outside/target.txt.
Ask the agent to read link → the content of /outside/target.txt comes
back.
Ask it to write to link → /outside/target.txt changes.
Ask it to edit link → /outside/target.txt changes.
A dangling symlink is the sharpest case: link -> /outside/new.txt, where /outside/new.txt does not exist yet. Writing through it creates the file
outside the workspace.
The cloud path has the same blind spot in packages/agent-tools/src/cloud/matrix-tools/path-guard.ts, where a dangling
link was treated as a missing file and compared lexically.
Impact
the agent reads and writes outside the workspace without any prompt
the permission layer compares paths lexically too, so these calls never
reach the approval prompt
a dangling symlink lets a write create a file at an arbitrary path
Expected
The tool resolves the target through its real location — following symlinks,
and following dangling ones by hand — and refuses it when that location is
outside the workspace.
The fence should only validate. pi should still receive the lexical absolute
path, byte for byte, because the ledger key has to stay aligned with pi's own resolveToCwd output. Changing the spelling would change the ledger key.
Notes and limits
the check and the open are two steps, so the link can be swapped in
between. Closing that race needs a lower-level open.
bash is not covered here. It is arbitrary execution by design; a check
there is advice, not a boundary.
a target that is already outside the workspace lexically keeps its current
approval flow. Turning that approval into a hard failure would break
cross-workspace reads users approve today, and that belongs to the
permission layer rather than here.
Activity