Skip to content

harden(worktree): symlink-plant defense in pruneStale#2173

Open
hernandez42 wants to merge 1 commit into
garrytan:mainfrom
hernandez42:hardening/worktree-safe-prune-v2-1783150955
Open

harden(worktree): symlink-plant defense in pruneStale#2173
hernandez42 wants to merge 1 commit into
garrytan:mainfrom
hernandez42:hardening/worktree-safe-prune-v2-1783150955

Conversation

@hernandez42

Copy link
Copy Markdown

Motivation

WorktreeManager.pruneStale() recursively deletes sub-directories under
.gstack-worktrees/ older than 1 hour. Today the only validation is an
mtime check — no path containment check.

In a shared CI / multi-tenant environment, an attacker could pre-plant a
symlink inside .gstack-worktrees/ that redirects fs.rmSync to a
directory outside worktreeBase.

Fix

Add safeInsideWorktrees() method that:

  1. Uses fs.realpathSync.native to resolve symlinks
  2. Computes path.relative(baseReal, real)
  3. Returns false if the result starts with .. or is absolute

Call safeInsideWorktrees() before every fs.rmSync in pruneStale().
Suspicious paths are logged and skipped instead of deleted.

Changes

  • lib/worktree.ts: Added safeInsideWorktrees() + call site in pruneStale()

Submitted by 璇玑-58 via security audit

Add safeInsideWorktrees() check before fs.rmSync in pruneStale().
Uses fs.realpathSync.native to resolve symlinks and verifies the
resolved path stays inside worktreeBase. Rejects any path that
escapes via '..' or absolute resolution.

Defends against: a pre-planted symlink inside .gstack-worktrees/
that redirects the rm target outside the worktree directory.

*Submitted by 璇玑-58 via security audit*
@trunk-io

trunk-io Bot commented Jul 4, 2026

Copy link
Copy Markdown

Merging to main in this repository is managed by Trunk.

  • To merge this pull request, check the box to the left or comment /trunk merge below.

After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant