fix(claude): hide secrets at every depth and worktree #207

Merged
claudiomaradonna merged 3 commits from sandbox-hardening into main 2026-11-09 18:55:31 +01:00
Collaborator

Why

Since #205 and #206 every repository on the template runs Claude Code's
Bash sandbox. Checks on 2026-11-09 found that the secret rules reached
only the project root's top level in the sandbox, and only the shell's
directory in the Read tool, so a .env or a key in a subdirectory or in
a worktree under .worktrees/, where all change work happens, stayed
readable. The validator also failed on the empty placeholders the
sandbox puts at absent CLAUDE paths, which Claude Code skips, and the
README named none of the settings that only a developer's own
~/.claude/settings.json can hold. Shipping failIfUnavailable,
allowAllUnixSockets, or an ask rule on the retry in the template was
set aside: each would change every adopter's machine in a way only its
developer can weigh.

What changed

Each project secret rule has a twin anchored at the project root,
Read(/**/<pattern>), that hides its files at every depth, worktrees
included, from sandboxed commands and the Read tool wherever the shell
is. *.pem and secrets.* keep to the top level, since deeper they name
CA bundles and source files, while *key*.pem is hidden everywhere.
Example .env files are refused too; git show HEAD:<path> reads them.

The validator counts a 0-byte CLAUDE file as absent.

The README's new "Your own sandbox settings" gives what a developer adds
to ~/.claude/settings.json, each with what it closes and costs, and
corrects the statements on absent policy files, git over SSH, and signed
commits; UPGRADING.md points upgrades to it.

How to verify

On Linux with the sandbox running, start Claude Code at the root of a
checkout of this branch, create fictional sub/.env.local,
sub/probe.key, and .venv/lib/python3.12/site-packages/certifi/cacert.pem,
and have it cat each in its Bash tool: the first two fail with
Permission denied and the CA bundle prints, where main prints all
three. bash tests/agent-config/claude-code-precedence.sh passes
"accepts an empty CLAUDE.md beside AGENTS.md", which fails on main
with "CLAUDE.md present without an AGENTS.md import".

On the tip, bash tests/verify-agent-config.sh (233 tests),
bash tests/check-work-folder.sh, .agents/hooks/shellcheck.sh repo,
prettier --check '**/*.md', scripts/verify-agent-config.sh, and
git diff --check main pass; the new settings assertion and the two
precedence tests fail on main. Not tried: macOS and WSL2.

Considerations

On Linux each sandboxed command takes about 0.1 s more, and more in a
large tree, while the sandbox finds the files the new rules name. A
setup step that copies an example .env file now falls to the retry
outside the sandbox.

## Why Since #205 and #206 every repository on the template runs Claude Code's Bash sandbox. Checks on 2026-11-09 found that the secret rules reached only the project root's top level in the sandbox, and only the shell's directory in the Read tool, so a `.env` or a key in a subdirectory or in a worktree under `.worktrees/`, where all change work happens, stayed readable. The validator also failed on the empty placeholders the sandbox puts at absent CLAUDE paths, which Claude Code skips, and the README named none of the settings that only a developer's own `~/.claude/settings.json` can hold. Shipping `failIfUnavailable`, `allowAllUnixSockets`, or an `ask` rule on the retry in the template was set aside: each would change every adopter's machine in a way only its developer can weigh. ## What changed Each project secret rule has a twin anchored at the project root, `Read(/**/<pattern>)`, that hides its files at every depth, worktrees included, from sandboxed commands and the Read tool wherever the shell is. `*.pem` and `secrets.*` keep to the top level, since deeper they name CA bundles and source files, while `*key*.pem` is hidden everywhere. Example `.env` files are refused too; `git show HEAD:<path>` reads them. The validator counts a 0-byte CLAUDE file as absent. The README's new "Your own sandbox settings" gives what a developer adds to `~/.claude/settings.json`, each with what it closes and costs, and corrects the statements on absent policy files, git over SSH, and signed commits; `UPGRADING.md` points upgrades to it. ## How to verify On Linux with the sandbox running, start Claude Code at the root of a checkout of this branch, create fictional `sub/.env.local`, `sub/probe.key`, and `.venv/lib/python3.12/site-packages/certifi/cacert.pem`, and have it `cat` each in its Bash tool: the first two fail with `Permission denied` and the CA bundle prints, where `main` prints all three. `bash tests/agent-config/claude-code-precedence.sh` passes "accepts an empty CLAUDE.md beside AGENTS.md", which fails on `main` with "CLAUDE.md present without an AGENTS.md import". On the tip, `bash tests/verify-agent-config.sh` (233 tests), `bash tests/check-work-folder.sh`, `.agents/hooks/shellcheck.sh repo`, `prettier --check '**/*.md'`, `scripts/verify-agent-config.sh`, and `git diff --check main` pass; the new settings assertion and the two precedence tests fail on `main`. Not tried: macOS and WSL2. ## Considerations On Linux each sandboxed command takes about 0.1 s more, and more in a large tree, while the sandbox finds the files the new rules name. A setup step that copies an example `.env` file now falls to the retry outside the sandbox.
The sandbox resolved the project's ./ deny rules at the project root's
top level, and the Read tool at the shell's directory, so a secret in a
subdirectory or in a worktree under .worktrees/ stayed readable to
sandboxed commands, and the main checkout's to the Read tool from a
worktree. A twin anchored at the project root, /**/<pattern>, covers
every depth for both, whatever the shell's directory.

*.pem and secrets.* keep to the root's top level, where CA bundles and
source modules do not sit, with *key*.pem at every depth. The example
.env files lose their carve-outs, which no root-anchored rule honors;
git show HEAD:<path> reads a tracked one.
Claude Code's sandbox holds an absent write-denied path, such as the
CLAUDE files, by a 0-byte placeholder while each command runs, and a
session killed mid-command leaves it behind. Claude Code 2.1.319 skips
an empty CLAUDE file and still loads AGENTS.md, so the validator's
error, that Claude Code would ignore AGENTS.md, was false, and failed
every run in the main checkout from inside the sandbox.
docs: explain secret depth and developer settings
All checks were successful
Maintenance / checks (pull_request) Successful in 4m8s
Maintenance / checks (push) Successful in 3m50s
6ac74e03ed
The README now says how far the secret rules reach, where *.pem and
secrets.* stop, and how a tracked example .env file reads through git;
that absent policy files are write-denied by placeholders, which a
killed session leaves behind; and that a signed commit, and git over
SSH on Linux as on macOS, fall to the retry.

It also lists the settings a repository cannot ship, or should not for
everyone, with what each closes and costs, and UPGRADING.md has the
upgrade tell the user of the change.
claudiomaradonna deleted branch sandbox-hardening 2026-11-09 18:55:35 +01:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
claudiomaradonna/agent-foundry!207
No description provided.