Skip to content

fix(sandbox): validate requested branch name before shelling out in clone#4499

Merged
pedrofrxncx merged 1 commit into
mainfrom
fix/clone-branch-shell-injection
Jul 13, 2026
Merged

fix(sandbox): validate requested branch name before shelling out in clone#4499
pedrofrxncx merged 1 commit into
mainfrom
fix/clone-branch-shell-injection

Conversation

@pedrofrxncx

@pedrofrxncx pedrofrxncx commented Jul 13, 2026

Copy link
Copy Markdown
Collaborator

Source

A bug found by reading packages/sandbox/daemon/setup/clone.ts while looking at recent churn in packages/sandbox/daemon/git/checkout-branch.ts (#4472, #4470 hardened the sibling checkout path against the same class of issue).

The bug

spawnClone builds sh -c git commands (ls-remote, clone --branch, checkout -B) by interpolating the config-supplied git.repository.branch directly into the command string, with no validation. Git allows shell metacharacters (;, $(…), backticks, …) in ref names, so a branch value like x;touch pwned runs touch pwned as a second shell command the moment spawnClone does its first ls-remote probe — during the very first clone of a sandbox.

The same interpolation for the remote-controlled default branch (base, read from ls-remote --symref output) was already guarded by a hand-rolled isSafeRefName allowlist in this file, and the analogous requested-branch value in the sibling checkout path (git/checkout-branch.ts) was already hardened in #4472/#4470 — this fixes the one remaining unguarded call site, and folds the two independent ref-name allowlists (clone.ts's isSafeRefName and git/ref-name.ts's assertValidRemoteBranchName) into the single validator in git/ref-name.ts.

Regression test

packages/sandbox/daemon/setup/clone.test.ts"rejects a malicious requested branch instead of shelling it out" passes a branch of x;touch <marker> into spawnClone and asserts the marker file is never created and spawnClone returns a non-zero code instead of running the injected command.

To verify

bun test packages/sandbox/daemon/setup/clone.test.ts
bun test packages/sandbox/daemon/git/ref-name.test.ts

Checks run locally

  • bun run fmt
  • bunx tsc --noEmit in packages/sandbox
  • bun test packages/sandbox/daemon/setup/clone.test.ts (10 pass)
  • bun test packages/sandbox/daemon/git/ref-name.test.ts (2 pass)
  • bun test packages/sandbox/daemon/git/checkout-branch.test.ts (5 pass, unaffected by the shared validator change)

Full CI will run the rest of the suite.


Summary by cubic

Validate config‑supplied branch names in the sandbox clone path before building sh -c git commands. This prevents command injection via git.repository.branch and unifies ref-name validation in git/ref-name.ts.

  • Bug Fixes

    • spawnClone rejects invalid/malicious branch names with isValidRemoteBranchName and exits non‑zero.
    • Use the same validator when inspecting the remote default branch (base) to avoid unsafe interpolation.
    • Added a regression test to ensure injected shell payloads (e.g., x;touch <file>) never execute.
  • Refactors

    • Replaced local isSafeRefName with shared isValidRemoteBranchName/assertValidRemoteBranchName in git/ref-name.ts.
    • Expanded validator tests and edge cases (e.g., reject a//b).

Written for commit c94c649. Summary will update on new commits.

Review in cubic

…lone

git.repository.branch is config-supplied and gets interpolated straight into
sh -c git commands in spawnClone (ls-remote/clone/checkout) with no
validation, unlike the sibling checkout path (git/checkout-branch.ts) which
already rejects unsafe ref names before shelling out. Since git allows shell
metacharacters in ref names, a branch like `x;touch pwned` runs as a second
shell command during the very first clone.

Consolidated clone.ts's own isSafeRefName (already used for the
remote-controlled default-branch fetch) into the shared
git/ref-name.ts validator so both call sites share one allowlist, and used
it to validate the requested branch too.
@pedrofrxncx pedrofrxncx merged commit 53cc3ed into main Jul 13, 2026
14 checks passed
@pedrofrxncx pedrofrxncx deleted the fix/clone-branch-shell-injection branch July 13, 2026 21:33
decocms Bot pushed a commit that referenced this pull request Jul 13, 2026
PR: #4499 fix(sandbox): validate requested branch name before shelling out in clone
Bump type: patch

- @decocms/sandbox (packages/sandbox/package.json): 1.14.4 -> 1.14.5

Deploy-Scope: both
tlgimenes added a commit that referenced this pull request Jul 13, 2026
Conflict: main's #4499 (validate config-supplied branch before shelling
out in clone) vs this branch's structured-command clone rewrite.
Resolution: adopt main's shared isValidRemoteBranchName (git/ref-name.ts)
everywhere — the branch's local isSafeRefName copy and its tests are
superseded; keep main's new requested-branch guard (argv can't be
shell-injected anymore, but flag-shaped/ref-format-invalid names are
still rejected before reaching git); comments reworded for the argv era;
main's malicious-branch tests adapted to the required askpassPath dep.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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