Files
kandev/apps/backend/internal/worktree/copyfiles
João Salavisa 19c3771cf0 fix: validate destination parent chain in copyfiles byte copy (#1801)
* fix: validate destination parent chain in copyfiles byte copy

copyState.copyFile wrote into the target worktree via os.MkdirAll +
os.Create with no destination-parent symlink validation, unlike the
sibling paths that were hardened for exactly this: symlinkMatch calls
validateParentChain and remote writeOneEntry calls secureDestination.

A worktree whose HEAD ships a directory segment as a symlink (a fork-PR
head can set a tree entry to mode 120000 pointing at an absolute host
path) let MkdirAll follow the link and os.Create write the source file's
bytes outside the worktree as the backend user — a zero-click,
content-driven arbitrary-file-write reachable on the PR-review path
(copyConfiguredFiles -> copyfiles.Copy during worktree creation).

Route the byte copy through the same secureDestination hardening: it
runs validateParentChain, creates the parent dirs, and re-confirms
containment via EvalSymlinks before returning a destination guaranteed
inside the worktree. A rejected destination emits a non-fatal warning,
mirroring symlinkMatch. The leaf os.Lstat skip-if-exists behavior is
preserved (only a symlinked parent was the escape vector).

Add byte-copy regression tests: a symlinked parent is rejected and
nothing is written outside the worktree, a leaf symlink is still
skipped, and a clean nested copy still works.

* fix: canonicalize copyfiles target root before containment checks

copyFile passed the raw s.targetDir into secureDestination, which
EvalSymlinks-resolves the created parent and then Rel-checks it against
that root. When the worktree is reached through a symlinked ancestor
(macOS /tmp -> /private/tmp, or a user-symlinked tasks dir) the canonical
parent looks like it escapes the non-canonical root, so every legitimate
byte copy is silently rejected.

Resolve targetDir once in Copy (falling back to the raw path when it does
not exist yet), mirroring the EvalSymlinks WriteEntries already performs.
Add a regression test that routes the target through a symlinked ancestor
and asserts the copy still lands at the real location.

---------

Co-authored-by: Kandev Agent <agent@kandev.dev>
2026-07-19 16:12:44 +01:00
..