Uh oh!
There was an error while loading. Please reload this page.
Reject symlink aliases for cancel source and destination - #94
Conversation
Using realpath-with-leaf-dereference on the user-provided mv source and destination lets a symlink aliasing <loop>/state.md or <loop>/cancel-state.md pass authorization. For the destination case the vulnerability is exploitable: `mv <loop>/state.md /tmp/link` (where /tmp/link -> <loop>/cancel-state.md) canonicalizes to the expected path and passes the check, but `mv` replaces the link at /tmp/link rather than creating <loop>/cancel-state.md, corrupting loop state and depositing state.md outside the loop dir. Introduce canonicalize_path_prefix in project-root.sh that resolves symlinks ONLY in the parent directory and preserves the basename verbatim. Symlinked project prefixes (e.g. /var vs /private/var) still match a canonical expected path, but a symlink at the leaf no longer impersonates the real filename. Rewire is_cancel_authorized to use the prefix-only helper for both src and dest, and document why the distinction matters in-place. Update the on-disk src_original probe to reference canonical_loop_dir so the symlink-rejection check runs against the real path rather than any user-supplied non-canonical form. Add two regression tests covering the destination and source symlink alias attacks.
SihaoLiu
commented
Apr 17, 2026
@codex review this PR |
Codex Review: Didn't find any major issues. Hooray! ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
The main FILE_PATH vs CORRECT_PATH comparison in loop-read-validator.sh and loop-write-validator.sh previously used canonicalize_path, which dereferences symlinks at the leaf. A planted symlink at the correct filename inside the loop dir would then canonicalize to its target and let the validator approve a Read or Write that follows the link out of the loop dir, expanding Claude's effective file-access reach beyond what the hook intends to permit. Switch both validators to canonicalize_path_prefix so a symlinked project ancestor still resolves correctly (the original bug this feature fixes) while a symlink at the leaf no longer impersonates the expected filename. This matches the same discipline applied to is_cancel_authorized and preserves the intended semantics that the basename is compared verbatim. Methodology-analysis checks in these validators intentionally keep full-path realpath because they use prefix-containment (path starts with loop-dir/) rather than equality, which correctly catches symlinks escaping the loop dir.
SihaoLiu
commented
Apr 17, 2026
@codex review this PR as entirety |
Codex Review: Didn't find any major issues. You're on a roll. ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Uh oh!
There was an error while loading. Please reload this page.
Summary
Follow-up to the canonicalize-paths work already merged into dev via #93. Codex review on that PR flagged a P1 path-restriction bypass: using full-path
realpathon the user-providedmvsource and destination inis_cancel_authorizedlets a symlink aliasing<loop>/state.mdor<loop>/cancel-state.mdpass authorization. For the destination case this is exploitable —mv <loop>/state.md /tmp/linkwhere/tmp/link -> <loop>/cancel-state.mdcanonicalizes to the expected path and passes the check, butmvreplaces the link at/tmp/linkrather than creating<loop>/cancel-state.md, corrupting loop state and depositingstate.mdoutside the loop dir.canonicalize_path_prefixinhooks/lib/project-root.shthat resolves symlinks only in the parent directory and preserves the basename verbatim. Symlinked project prefixes (e.g./varvs/private/var) still match a canonical expected path, but a symlink at the leaf no longer impersonates the real filename.is_cancel_authorizedto use the prefix-only helper for bothsrcanddest. Document the distinction in-place. Update the on-disksrc_originalprobe to referencecanonical_loop_dirso the symlink-rejection check runs against the real path.Test plan
HELPER TEST 9covers the destination symlink alias attackHELPER TEST 10covers the source symlink alias attackHELPER TEST 8(symlinked prefix) still passes, proving prefix resolution still worksbash tests/run-all-tests.sh-> 1725 passed / 0 failed