anthropics / anthropics/claude-code

[BUG] 2.1.278: a file redirection on a single excluded command defeats sandbox.excludedCommands (gh fails TLS)

Open
#95,532 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

area:sandbox bug has repro platform:macos regression
Dominant language
TypeScript
Stars
147k
Forks
24k
PR merge metrics
PR metrics pending

Description

Preflight Checklist
  • I have searched existing issues and this hasn't been reported yet
  • This is a single bug report
  • I am using the latest version of Claude Code
What's Wrong?

With sandbox.excludedCommands: ["gh *"], a bare gh … call runs outside the sandbox as documented. The same single command with a file redirection on stdout or stderr is sandboxed instead, and on macOS then fails TLS verification (x509: OSStatus -26276, the documented Go/Seatbelt failure that excludedCommands exists to work around).

A redirection is not a command. Under the 2.1.277 rule ("every part must now match"), gh api user > out.json has exactly one part, and that part matches gh *.

Measured on 2.1.278, each line a separate Bash tool call, same session:

Command Runs outside the sandbox?
gh api user --jq .login yes
cd /repo && gh api user --jq .login yes
gh api user --jq .login && gh api user --jq .name yes
gh api user --jq .login 2>&1 yes
gh api user --jq .login > "$TMPDIR/out.txt" no
gh api user --jq .login > /path/literal/out.txt (no variable expansion) no
gh api user --jq .login > /dev/null no
gh api user --jq .login 2>/dev/null no

So a descriptor duplication (2>&1) is fine, but any redirection to a file, including /dev/null, drops the exclusion. $(gh …) and gh … | head are also sandboxed, which I take to be the intended 2.1.277 behaviour and is not what this report is about.

This matters because writing gh's JSON to a file and post-processing it in a separate call is the natural workaround for the "every part must match" rule (you cannot pipe, so you redirect). That workaround does not work either, which leaves --jq as the only way to get structured output out of an excluded gh without a per-call sandbox bypass.

What Should Happen?

A redirection should not count as a non-matching part. gh api user > out.json should run outside the sandbox exactly like gh api user, as it did on earlier builds (see the regression note).

If redirections are excluded deliberately, the settings reference and the "Go-based CLIs fail TLS verification on macOS" troubleshooting entry should say so, since that entry currently names excludedCommands as the remedy with no caveat.

Error Messages/Logs
# excluded — works
$ gh api user --jq .login
potiuk

# same command, stdout redirected — sandboxed
$ gh api user --jq .login > /private/tmp/claude-501/.../scratchpad/probe-h.txt
Get "https://api.github.com/user": tls: failed to verify certificate: x509: OSStatus -26276

# stderr redirected — sandboxed (error is swallowed, exit 1)
$ gh api user --jq .login 2>/dev/null
(exit 1)

# descriptor dup — excluded, works
$ gh api user --jq .login 2>&1
potiuk
Steps to Reproduce
  1. macOS, sandbox enabled, project or user settings:
    {
      "sandbox": {
        "enabled": true,
        "excludedCommands": ["gh *"],
        "network": { "allowedDomains": ["api.github.com", "github.com"] }
      }
    }
    
  2. Log in with gh auth login in a normal terminal.
  3. As one Bash tool call: gh api user --jq .login → prints the login.
  4. As a separate Bash tool call: gh api user --jq .login > /dev/nulltls: failed to verify certificate: x509: OSStatus -26276.
  5. As a separate Bash tool call: gh api user --jq .login 2>&1 → prints the login again.

Only the redirection differs between steps 3 and 4.

Claude Model

Other (Fable 5.1)

Is this a regression?

Yes, this worked in a previous version

Last Working Version

Not bisected. #82109 shows gh --version > ~/sbprobe running excluded on an earlier build (its case 1), so the redirection form was exempted before the 2.1.277 matching change.

Claude Code Version

2.1.278 (Claude Code)

Platform

Anthropic API

Operating System

macOS (26.6, Apple silicon)

Terminal/Shell

Other (Ghostty, zsh)

Additional Information
  • gh 2.x from Homebrew, linked with go1.26.5. Python urllib through the same sandbox proxy returns 200, so the proxy and certificates are fine; the failure is Go's Security.framework verification with trustd unreachable (#85857). GODEBUG=x509usefallbackroots=1 and SSL_CERT_FILE do not help, so excludedCommands really is the only remedy for gh inside the sandbox.
  • Related: #95455 (single command with a value-taking pre-subcommand flag also loses the exclusion after 2.1.277 — same over-matching family, different trigger), #82109 (loops), #81157 (the compound-command bypass that 2.1.277 fixed; this is not a request to revert that).

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reproducing the separate Bash tool calls using sandbox.excludedCommands with gh api user, file stdout or stderr redirection, and descriptor duplication. Then trace the sandbox command-matching entry point for the 2.1.277 matching change; the report names no source file or test. Done means file redirections preserve the gh exclusion without changing the intended compound-command behavior, with regression coverage for the listed forms.

Written by the indexing model from the issue text.

Assessment

Tech stack
macos, shell, typescript
Domain
cli, operating-systems, security
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.