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.
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:Node's recursive
mkdirSyncfollows a symlink at the final component (nolstat). If.worktreesis a symlink,path.joinresolves through it, andgit worktree addpopulates 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 doingrm -rf .worktrees && ln -s ~/.config .worktrees— causes the next rescue's worktree (the Codexworkspace-writeroot) 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-destproduced/tmp/attacker-dest/codex-FAKE/populated with HEAD content; the~/.sshvariant also reproduced (then cleaned up).Severity
CRITICAL once
createWorktreeSessionis 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--worktreeflag 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.worktreespath and verify the resolved realpath is contained within repoRoot. Or create worktrees under an app-ownedmkdtempdir rather than inside the repo.Refs #12, #13 (umbrella), openai#135, openai#137.