Skip to content

security(critical): .worktrees/ symlink traversal redirects worktree creation to arbitrary host location #14

Description

@axisrow

Security finding from the adversarial review of the worktree-isolation library layer (#12, commit 1a9eb11). Reproduced empirically.

Vulnerability

createWorktree (plugins/codex/scripts/lib/git.mjs) does:

const worktreesDir = path.join(repoRoot, ".worktrees");
fs.mkdirSync(worktreesDir, { recursive: true });
...
const worktreePath = path.join(worktreesDir, `codex-${ts}`);
gitChecked(repoRoot, ["worktree", "add", worktreePath, "-b", branch]);

Node's recursive mkdirSync follows a symlink at the final component (no lstat). If .worktrees is a symlink, path.join resolves through it, and git worktree add populates the symlink target with a full git checkout.

Attack

An attacker who can place one symlink at <repoRoot>/.worktrees — via a crafted/tarballed repo that ships .worktrees → ~/.ssh, a co-resident user on a shared dev box, or a prior malicious Codex run doing rm -rf .worktrees && ln -s ~/.config .worktrees — causes the next rescue's worktree (the Codex workspace-write root) to be created in ~/.ssh/, ~/.config/, ~/Library/LaunchAgents/, etc. That is sandbox escape + a persistence/credential-injection primitive.

Reproduced end-to-end: .worktrees → /tmp/attacker-dest produced /tmp/attacker-dest/codex-FAKE/ populated with HEAD content; the ~/.ssh variant also reproduced (then cleaned up).

Severity

CRITICAL once createWorktreeSession is wired into the rescue flow (it currently has no production callers — #12 landed the library layer only). Not a live exploitable surface today, but must be closed before the --worktree flag reaches the CLI.

Fix

Before mkdirSync(worktreesDir), lstatSync(worktreesDir) and bail (or remove the offending symlink) if it is a symlink. Better: reject symlinked components in the whole .worktrees path and verify the resolved realpath is contained within repoRoot. Or create worktrees under an app-owned mkdtemp dir rather than inside the repo.

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