Skip to content

fix(file): reject dangling-symlink writes outside workspace - #90

Closed
cursor[bot] wants to merge 1 commit into
mainfrom
cursor/high-severity-issues-338b
Closed

cursor[bot] wants to merge 1 commit into
mainfrom
cursor/high-severity-issues-338b

Conversation

@cursor

@cursor cursor Bot commented Sep 15, 2026

Copy link
Copy Markdown

Bug and impact

With workspace_only (on by default), write_file walked to the first existing ancestor when realpath failed. A dangling symlink in the workspace therefore passed the prefix check, and fopen("w") followed it and created or truncated a host file outside the workspace.

Camera capture used the same ancestor walk for caller-supplied JPEG paths.

Concrete trigger (main d367477)

  1. Workspace /tmp/ws, sandbox/workspace_only on.
  2. Create a dangling link: python3 -c 'import os; os.symlink("/tmp/sc_pwned","leak")' (allowlist skips this; same first step as fix(sandbox): Landlock + relative-token block for workspace FS escapes #74).
  3. write_file path=/tmp/ws/leak content=pwned.
  4. /tmp/sc_pwned is created on the host. read_file of an existing outside symlink is already denied via realpath.

Same walk applies to camera_capture path=.

Distinctness

Fix

  • If the requested path is an unresolvable symlink, deny (do not fall back to the ancestor).
  • write_file opens with O_NOFOLLOW when workspace_only is set so a TOCTOU swap cannot follow a link.
  • Camera path_inside_workspace uses the same dangling-symlink deny.

Test plan

  • CI=true make test_file — test_file_write_dangling_symlink_rejected
  • CI=true make test_hardware_camera — test_output_dangling_symlink_rejected
  • CI=true make test_shell

Does not re-open #66/#74/#76/#82 (still present on main; tracked by #89).

Open in Web View Automation 

write_file treated a dangling workspace symlink as "ancestor is in
bounds" and fopen("w") followed it onto the host. Camera capture used
the same ancestor walk. Open with O_NOFOLLOW and fail closed on
unresolvable links.

Co-authored-by: esadrianno <esadrianno@gmail.com>
adriannoes added a commit that referenced this pull request Sep 21, 2026
Ancestor lookup is membership only: do not fopen the resolved parent,
which truncated a workspace file treated as a directory. Dangling
symlinks fail closed via lstat and O_NOFOLLOW instead of creating
host files outside the workspace.

Refs: #67, #90
adriannoes added a commit that referenced this pull request Sep 21, 2026
… paths

workspace_only with an empty root no longer treats every path as allowed.
Dangling symlink capture outputs are rejected instead of creating host
files outside the workspace.

Refs: #91, #90
@adriannoes

Copy link
Copy Markdown
Collaborator

Superseded by #94 (já em development).

@adriannoes adriannoes closed this Sep 21, 2026
@adriannoes
adriannoes deleted the cursor/high-severity-issues-338b branch September 22, 2026 21:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants