Repository navigation
Commit a6658d8
fix(security): reject symlink-mode blobs in the worktree patch
Closes #15 (HIGH). applyWorktreePatch captures the worktree diff with
`git diff --cached --binary --output=<file>` and replays it with
`git apply --index` in repoRoot. A symlink created in the (attacker/Codex-
controlled) worktree — `ln -s ~/.ssh/authorized_keys ./steal` — is captured as
a mode-120000 diff entry with the target path verbatim. `git apply --index`
then recreates the symlink in repoRoot pointing at the attacker-chosen absolute
host path. Subsequent cat/git-show/editor-open exfiltrates the file; >> appends.
The byte-preserving `--output` transfer does not help — the symlink target is
legitimately part of the diff. `git apply` rejects literal "../" filename paths,
but symlink-mode entries bypass that guard: the repo-side filename is a normal
path; only the symlink target points outside.
Attack reproduced before the fix: `ln -s /etc/passwd ./steal` in the worktree
produced a patch with `new file mode 120000`, and `git apply --index` recreated
`<repoRoot>/steal -> /etc/passwd`.
Fix: after writing the patch file, scan it for a `mode 120000` line. If found,
do NOT apply — return {applied:false} with a message directing the user to
inspect/copy the symlinks manually from the worktree. The leave-branch invariant
is preserved (the worktree stays; nothing is destroyed).
Regression test: plant a symlink in the worktree, call keep, assert applied
=== false and the symlink is NOT recreated in repoRoot.
Verified: worktree 14/14; full npm test 157 pass / 4 pre-existing (unchanged).
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_0189cGaohsYdpkB4otKZnpAt1 parent 5a19596 commit a6658d8
2 files changed
Lines changed: 65 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
377 | 377 | | |
378 | 378 | | |
379 | 379 | | |
| 380 | + | |
| 381 | + | |
| 382 | + | |
| 383 | + | |
| 384 | + | |
| 385 | + | |
| 386 | + | |
| 387 | + | |
| 388 | + | |
| 389 | + | |
| 390 | + | |
| 391 | + | |
| 392 | + | |
| 393 | + | |
| 394 | + | |
380 | 395 | | |
381 | 396 | | |
382 | 397 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
333 | 333 | | |
334 | 334 | | |
335 | 335 | | |
| 336 | + | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
| 340 | + | |
| 341 | + | |
| 342 | + | |
| 343 | + | |
| 344 | + | |
| 345 | + | |
| 346 | + | |
| 347 | + | |
| 348 | + | |
| 349 | + | |
| 350 | + | |
| 351 | + | |
| 352 | + | |
| 353 | + | |
| 354 | + | |
| 355 | + | |
| 356 | + | |
| 357 | + | |
| 358 | + | |
| 359 | + | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
| 369 | + | |
| 370 | + | |
| 371 | + | |
| 372 | + | |
| 373 | + | |
| 374 | + | |
| 375 | + | |
| 376 | + | |
| 377 | + | |
| 378 | + | |
| 379 | + | |
| 380 | + | |
| 381 | + | |
| 382 | + | |
| 383 | + | |
| 384 | + | |
| 385 | + | |
0 commit comments