Skip to content

security(high): symlink-mode blob in worktree patch replays a host-file symlink into repoRoot #15

Description

@axisrow

Security finding from the adversarial review of #12 (commit 1a9eb11). Reproduced empirically.

Vulnerability

applyWorktreePatch (plugins/codex/scripts/lib/git.mjs) captures the worktree diff with git diff --cached --binary --output=<patch> and replays it with git apply --index <patch> in repoRoot. A symlink created inside the (attacker/Codex-controlled) worktree is captured as a mode-120000 diff entry with the target path stored verbatim, and git apply --index recreates it in repoRoot pointing at the attacker-chosen absolute host path.

The byte-preserving --output transfer does not help — the symlink target is legitimately part of the diff content.

Attack

  1. Inside the worktree, create ln -s ~/.ssh/authorized_keys ./steal (or any host path), git add -A.
  2. git diff --cached --binary HEAD --output <patch> captures ./steal with mode 120000 and target /Users/.../.ssh/authorized_keys.
  3. git apply --index <patch> in repoRoot recreates <repoRoot>/steal -> ~/.ssh/authorized_keys.

Once the symlink exists in repoRoot, any subsequent cat ./steal, git show :./steal, editor-open, or git add of the path lets the attacker read the host file; with >>, append.

Reproduced exactly: after apply, <repoRoot>/authorized_keys_link -> /Users/axisrow/.ssh/authorized_keys existed.

Note: git apply rejects patches with literal ../ filename paths, but symlink-mode entries bypass that guard because the repo-side filename is a normal path — only the symlink target points outside.

Severity

HIGH once wired (no production callers today). Exfiltration + append primitive via the keep-transfer path.

Fix

After capture but before git apply, reject the patch if any added blob has mode 120000 (symlink). Alternatively, post-apply scan repoRoot for newly-created symlinks and abort. git apply --no-symlink-refs does not exist as a portable flag, so the pre-apply mode check is the reliable guard.

Refs #12, #13 (umbrella), openai#135, openai#137.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions