sandbox: per-project trusted-hooks allowlist — 2.1.150 denies all .git/hooks/ writes with no opt-out

Resolved 💬 2 comments Opened May 23, 2026 by spectre6000 Closed Jun 24, 2026

Summary

In Claude Code 2.1.150, the agent sandbox's binary-default denyWithinAllow blocks all writes to hooks/ and .git/hooks/ inside any project. There's no documented setting that overrides this deny — sandbox.filesystem.allowWrite is additive-merge per the docs and doesn't override denyWithinAllow.

This blocks the legitimate, common workflow of installing project-shipped git hooks (e.g., a make install-hooks step that copies hooks/pre-push.git/hooks/pre-push).

Reproduction

In any project with checked-in hooks/ you want the agent to install:

$ cp hooks/pre-push .git/hooks/pre-push
cp: .git/hooks/pre-push: Operation not permitted

Widening sandbox.filesystem.allowWrite in .claude/settings.local.json to include the relevant paths has no effect:

"sandbox": {
  "filesystem": {
    "allowWrite": [".", "/abs/path/to/repo/hooks", "/abs/path/to/repo/.git/hooks"]
  }
}
$ cp hooks/pre-push .git/hooks/pre-push
cp: .git/hooks/pre-push: Operation not permitted

Why the deny exists (and why the all-or-nothing shape is wrong)

The .git/hooks/ write deny is a sensible security mitigation — git hooks are a classic persistence vector for prompt-injected malicious agents. A post-commit or pre-push hook installed by a compromised agent runs on every git event, with user credentials, outliving the agent session.

But the deny is blanket — it blocks all writes to .git/hooks/ regardless of source. There's no way to express "this project's checked-in hooks/ directory is a trusted source; the agent may install from there." Users with hook-installation workflows are forced into one of:

  • dangerouslyDisableSandbox: true per command (fights user-set policy that often explicitly revokes blanket bypass)
  • sandbox.enabled: false session-wide (over-broad)
  • Run the install manually outside the agent (defeats the workflow)
  • Downgrade to 2.1.148 (loses other 2.1.150 improvements)

Proposed feature

Add a per-project trusted-hooks allowlist setting. Example schema:

"sandbox": {
  "filesystem": {
    "trustedHookSources": [
      "/abs/path/to/repo/hooks"
    ]
  }
}

Semantics: the agent may write to .git/hooks/<name> IF the content matches a file at <trustedHookSources entry>/<name> (or, less strict: IF the source path of a copy operation is in the allowlist). Other writes to .git/hooks/ stay denied.

Equivalently, a more general-purpose sandbox.filesystem.allowWriteWithinDeny that takes precedence over denyWithinAllow for explicitly-listed paths would work.

This restores the user-managed-hooks workflow without weakening the general defense against malicious hook implantation.

Related observations from the same 2.1.150 sandbox surface

(Filing these as context — happy to split into separate issues if preferred.)

  1. sandbox.network.allowedDomains adds hosts but doesn't disable TLS interception. Even with api.github.com in allowedDomains, the gh CLI fails with tls: failed to verify certificate: x509: OSStatus -26276. The binary contains strings mitmProxy, tlsTerminate, parentProxy under network config, suggesting a MITM HTTPS proxy intercepts and re-signs traffic with a cert gh's Go HTTP stack doesn't trust. (curl works fine, presumably because it uses macOS Secure Transport which trusts the MITM cert via keychain.) No documented setting to disable the interception or trust the synthetic cert.
  1. sandbox.network.allowMachLookup controls service LOOKUP but not REGISTRATION. Chromium fails to launch with bootstrap_check_in org.chromium.Chromium.MachPortRendezvousServer.<pid>: Permission denied (1100) even when allowMachLookup: ["org.chromium.*"] is set. Chromium's bootstrap_check_in is a Mach port REGISTRATION call, not a lookup. This blocks any agent workflow that runs Playwright / Puppeteer / similar local-Chromium tooling (e.g., a pre-push hook's smoke test).
  1. autoMode.allow rules don't override the OS-level sandbox. Adding allow rules to bypass the Self-Modification soft-deny works as documented (lets the agent edit .claude/settings*.json), but doesn't affect the lower-layer sandbox denies. The three permission layers (OS sandbox, Claude permission rules, auto-mode classifier) are independent; settings allow rules at layers 2 and 3 don't unblock layer 1.

Version

Claude Code 2.1.150 on macOS 26.3 (Darwin 25.3.0), Apple Silicon. Confirmed against npm view @anthropic-ai/claude-code published artifact (auto-update on a homebrew-installed copy).

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗