Skip to content

bug(safety): dangling symlink bypasses the path-consent fence (write escapes the boundary without consent) #1659

Open

Description

@danielgap

Before submitting

  • I searched open and closed issues and did not find a report of this problem.
  • I reviewed this report and removed credentials, tokens, private paths, hostnames, and other sensitive data.

Problem

canonicalizeTarget in lib/path-consent-fence.ts (lines 45-51) reattaches path components that realpathSync cannot resolve as lexical tails (resolve(real, ...missing.reverse())). A dangling symlink inside the session worktree therefore classifies as inside the boundary: realpathSync fails on the link itself, the component is pushed onto missing, and it is later reattached lexically against the resolved ancestor instead of being followed to its destination.

A write/edit through that symlink then follows the link and creates the file outside the boundary with no consent prompt. Bash is not fenced yet (#1305 slice 3), so an agent can create the symlink with ln -s and then write through it, defeating the fence's core promise (per-target consent for writes outside the session worktree).

Reported by the native review-reliability lens review of the PR #1610 candidate (R3-dangling-symlink-bypass, WARNING) as a non-blocking advisory; parent issue #1305.

Steps to reproduce

Synthetic fixture, no infrastructure required:

D=$(mktemp -d)
mkdir -p "$D/repo" "$D/outside"
ln -s "$D/outside/notes.txt" "$D/repo/alias"

Evaluate $D/repo/alias with the exact canonicalizeTarget algorithm:

  1. realpathSync("$D/repo/alias") throws — the destination does not exist yet (dangling link).
  2. alias is pushed onto missing; the probe walks up to $D/repo, which resolves.
  3. Canonical form = resolve("$D/repo", "alias") = $D/repo/alias → insideBoundary returns true → fence decision: pass.
  4. writeFileSync("$D/repo/alias", ...) follows the symlink at the OS level → the file is created at $D/outside/notes.txt.

Expected and actual behavior

Expected: a target that writes outside the session worktree through a symlink requires per-target consent (#1305 maintainer direction, 2026-09-30); at minimum, a dangling-symlink component must fail closed (confirm) rather than classify inside.

Actual: canonicalizeTarget reattaches the dangling component lexically, the fence passes, and write/edit create files outside the boundary without any prompt.

gentle-pi version

From source: branch feat/1305-path-tool-fence @ a67ac168 (PR #1610 — the fence code ships there)

Pi version

1.0.0

Operating system

Linux

Relevant logs or error output (optional)

Standalone run of the exact algorithm (fixture from the steps above):

canonical(root/alias) = /tmp/<fixture>/repo/alias
inside boundary?       true
write(root/alias) OK   → real file created at /tmp/<fixture>/outside/notes.txt

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    status:approvedIssue approved by maintainer; PR may be opened

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions