Skip to content

fix(sandbox): validate remote default branch before shelling out in checkout#4470

Merged
pedrofrxncx merged 1 commit into
mainfrom
fix/checkout-branch-default-branch-injection
Jul 11, 2026
Merged

fix(sandbox): validate remote default branch before shelling out in checkout#4470
pedrofrxncx merged 1 commit into
mainfrom
fix/checkout-branch-default-branch-injection

Conversation

@pedrofrxncx

@pedrofrxncx pedrofrxncx commented Jul 11, 2026

Copy link
Copy Markdown
Collaborator

Source

A bug found while reading recently-changed sandbox git code (no open issue covers this).

Payoff

Closes a command-injection vector: spawnCheckoutBranch's fork-from-default-branch path reads defaultBranch from the remote's own origin/HEAD symref and interpolates it, unvalidated, into sh -c git commands (fetch/checkout -B). Git's ref-format rules permit shell metacharacters (;, $(...), backticks) in ref names, so a repository whose default branch is attacker-named lets that attacker run arbitrary commands in the sandbox daemon process the moment a task requests a branch that doesn't exist locally or on the remote.

This is the same vulnerability class the team already fixed in the sibling fetchBaseBranch path (packages/sandbox/daemon/setup/clone.ts, with its own ad-hoc isSafeRefName allowlist and an explicit comment about the injection vector) — this file was just missed. Rather than duplicating that allowlist again, this reuses the existing assertValidRemoteBranchName guard already used for the same purpose in rebase-onto-base.ts and routes/git.ts.

Failure scenario + regression test

packages/sandbox/daemon/git/checkout-branch.test.ts adds a case that points a cloned repo's origin/HEAD at refs/heads/pwn;touch${IFS}<tmp>/INJECTED (a syntactically valid git ref name) and requests a checkout of a branch absent from both remote and local. Before the fix, spawnCheckoutBranch resolves without error (i.e. it shells the malicious ref straight into sh -c); after the fix it rejects with InvalidRemoteBranchNameError before the shell ever sees it.

Verify

bun test packages/sandbox/daemon/git/checkout-branch.test.ts

Checks run locally

  • bun run fmt — no changes needed
  • bun test packages/sandbox/daemon/git/checkout-branch.test.ts — 4 pass (confirmed the new test fails on the pre-fix code, then passes after the fix)
  • Full CI will run typecheck/lint across the monorepo

Summary by cubic

Validate the remote default branch name in sandbox checkout to block command injection via attacker-controlled origin/HEAD. Adds a guard before shell commands and a regression test to ensure spawnCheckoutBranch rejects malicious refs.

  • Bug Fixes
    • Validate defaultBranch in spawnCheckoutBranch using assertValidRemoteBranchName before running git fetch/git checkout -B.
    • Add test that sets origin/HEAD to a ref with shell metacharacters; now fails with InvalidRemoteBranchNameError and no commands run.

Written for commit 60815ef. Summary will update on new commits.

Review in cubic

…heckout

spawnCheckoutBranch's fork-from-default-branch path reads defaultBranch
from the remote's own origin/HEAD symref and interpolates it into sh -c
git commands without validation. Git's ref-format rules permit shell
metacharacters (;, $(...), backticks) in ref names, so a repo whose
default branch is attacker-named injects arbitrary commands into the
sandbox daemon. The sibling fetchBaseBranch path in clone.ts was already
hardened against this exact class with its own ad-hoc allowlist; this
path was missed. Reuse the existing assertValidRemoteBranchName guard
(already used in rebase-onto-base.ts and routes/git.ts) instead of adding
another ad-hoc check.
@pedrofrxncx pedrofrxncx merged commit 91260b7 into main Jul 11, 2026
14 checks passed
@pedrofrxncx pedrofrxncx deleted the fix/checkout-branch-default-branch-injection branch July 11, 2026 19:32
decocms Bot pushed a commit that referenced this pull request Jul 11, 2026
PR: #4470 fix(sandbox): validate remote default branch before shelling out in checkout
Bump type: patch

- @decocms/sandbox (packages/sandbox/package.json): 1.14.2 -> 1.14.3

Deploy-Scope: both
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