Skip to content

fix: reject generated directory symlinks - #169

Open
sylvesterkaczmarek wants to merge 5 commits into
openai:mainfrom
sylvesterkaczmarek:fix/reject-generated-directory-symlinks
Open

fix: reject generated directory symlinks#169
sylvesterkaczmarek wants to merge 5 commits into
openai:mainfrom
sylvesterkaczmarek:fix/reject-generated-directory-symlinks

Conversation

@sylvesterkaczmarek

@sylvesterkaczmarek sylvesterkaczmarek commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Summary

Make Fence's generated-path helpers fail closed instead of traversing symbolic-link components below their approved generated roots.

Problem

Fence restricts generated-directory management to reviewed repo-generated paths, RUNNER_TEMP, or TMPDIR, but the base helper validates only the literal path string. A path can therefore satisfy the approved prefix check while one of its filesystem components is a symlink to a different location.

For clear_generated_dir, this is especially subtle. If the final managed path is itself a symlink, GNU find does not follow that command-line symlink in the current invocation, so the clear can return successfully. Later writes such as cp "$binary" "$dist_dir/$name" or coverage output creation then follow the symlink and land outside the approved generated root. The same escape is possible when an intermediate component is a symlink.

remove_generated_path has a related intermediate-component case: removing a final symlink is safe because rm -rf removes the link itself, but a path such as <symlink-parent>/child traverses the parent symlink before rm reaches the child.

Evidence / reproduction

  • script/lib/common.bash::require_generated_path checks the literal path against approved prefixes but does not inspect filesystem components.
  • script/build calls clear_generated_dir for its distribution directory before copying release artifacts into that path.
  • script/test --coverage calls the same helper before writing coverage outputs.
  • Minimal final-link reproduction on the base helper: create an approved path such as $DIR/target/generated-link as a symlink to another directory, call clear_generated_dir on it, then write generated-link/file. The clear can return without clearing the target, while the subsequent write lands in the symlink target.
  • Minimal ancestor reproduction: use $DIR/target/generated-link/nested as the managed path. The literal path remains under $DIR/target, but filesystem resolution crosses generated-link before reaching nested.
  • The same ancestor shape matters for removal: remove_generated_path "$link/victim" can otherwise address a file in the symlink target, even though remove_generated_path "$link" itself is safe.
  • script/test-generated-paths now covers all three boundaries: final-link clearing is rejected, intermediate-link clearing/removal is rejected, and deleting the final symlink itself remains permitted while its target remains untouched.

Change

  • add one shared check for symbolic-link components below the trusted generated root
  • make clear_generated_dir reject both final and intermediate symlink components
  • make remove_generated_path reject symlink ancestors while still allowing safe removal of the final symlink itself
  • add focused regressions and run them from the normal offline script/test entrypoint

No normal generated-directory layout, artifact location, or cleanup behavior changes for ordinary non-symlink paths.

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.

1 participant