anthropics / anthropics/claude-code

[BUG] `sandbox.filesystem.denyWrite` silently ignores rules containing a mid-path wildcard (Linux)

Abierto
#92,684 0 comentarios 0 reacciones 0 asignados Ver en GitHub
area:sandbox area:security bug has repro platform:linux
Lenguaje dominante
Python
Estrellas
145k
Forks
23.1k
Métricas de merge de PR
Métricas de PR pendientes

Descripción

### Preflight Checklist

- [x] I have searched [existing issues](https://github.com/anthropics/claude-code/issues?q=is%3Aissue%20state%3Aopen%20label%3Abug) and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code

### What's Wrong?

On Linux, `sandbox.filesystem.denyWrite` enforces rules whose wildcards are **trailing**
(`/path/**`) and rules that are **fully literal**, but silently ignores any rule with a
wildcard in the **middle** of the path (`/path/**/file`, `/path/*/file`). No warning is
emitted for the specific rule, and the rule appears in `/status` as if it were active.

**`sandbox.filesystem.denyRead` does not have this problem.** The identical rule shapes, in the
same config section, are all enforced on the read side — including both mid-path forms. So this
is not a platform limitation on what the sandbox can express; the write path is missing
matching that the read path already performs.

The pattern is also honoured by `permissions.deny` at the tool layer, so a single rule can
block the `Write` tool while leaving the identical path writable to any Bash subprocess. That
asymmetry is what makes this dangerous rather than merely surprising: the rule looks like it
is working when tested through the tool, and is not working where it matters.

`/status` and `claude doctor` do surface a warning for this (see below), so the limitation is
known; this issue is to report the specific shape, the read/write asymmetry, and the
fail-open behaviour.

### Environment

- Claude Code `2.1.263`, commit `37ae3f38d765`
- Platform `linux-arm64`, `node:22-bookworm-slim` in Docker (Docker Desktop for Mac)
- Sandbox enabled via managed settings at `/etc/claude-code/managed-settings.json`, with
`sandbox.enabled: true`, `sandbox.allowUnsandboxedCommands: false`, and
`sandbox.enableWeakerNestedSandbox: true` (required for bubblewrap nested inside Docker)

### Hypothesis

The `denyRead` results rule out the obvious explanation. Mid-path globs are clearly
expressible — the read side matches them and denies with `EACCES` — so this is not a limit on
what bubblewrap can represent.

What the errnos suggest instead is that `denyWrite` has two enforcement paths and no glob
matcher on either:

- literal path → `EACCES`, a per-path deny
- trailing `**` → `EROFS`, the subtree remounted read-only

A trailing `**` reduces cleanly to "this directory prefix", and a literal path needs no
matching at all. A mid-path wildcard fits neither, so it appears to fall through both branches
and be dropped, where the read side would have run it through its path matcher.

If that is right, the fix is to route `denyWrite` rules through the same matcher `denyRead`
already uses. A cheap interim fix would be to validate rules at load time and refuse to start
when a rule matches neither branch, so the failure is visible instead of silent.

### The existing warning

`/status` shows:

```
⚠ Glob patterns in sandbox permission rules are not fully supported on Linux
```

and `claude doctor` gives the detail:

```
- Glob patterns in sandbox permission rules are not fully supported on Linux
Fix: Found 17 pattern(s): Edit(//workspace/**/.git/hooks/**), Edit(//workspace/**/.git/config),
Edit(//workspace/**/.mcp.json) (14 more). On Linux, glob patterns in Edit/Read rules will be ignored.
```

The warning correctly identifies the affected rules. Two things would make it more actionable:

1. **"will be ignored" overstates it, in a way that hides the real risk.** The listed `Edit`
rules are still enforced at the tool layer; it is only their sandbox-derived counterpart
that is dropped. The precise statement is "these rules will not be enforced against
subprocesses" — which is the part a reader needs to act on, and is easy to miss when
testing the rule through the agent appears to confirm it works.
2. **It says `Edit/Read`, but `Read` rules are fine and `denyWrite` is not mentioned.** Per the
tables above, the read side honours globs; explicit `denyWrite` rules have the defect and
aren't named. Naming the write surfaces, and listing which of the 17 are affected, would
let a config author fix them.

### Why it matters

The documented guidance for protecting host-executed configuration inside a workspace —
git hooks, `.pre-commit-config.yaml`, `.envrc`, `.npmrc`, shell startup files — is a
`denyWrite` rule. In a multi-repo workspace those paths only exist under a wildcard
(`/workspace//.git/hooks`), so the wildcard form is the only maintainable way to
express them; enumerating every repo literally does not scale and silently rots as repos are
added.

With mid-path globs dropped, those paths are writable by any subprocess.

### Prior report

Submitted through `/bug` on 2026-09-06, receipt `613a9fb3-3860-4b55-8968-d566e485f315`.
Filing here for a trackable reference.

### What Should Happen?

`/workspace/**/probe.txt` should block a write to `/workspace/docs/probe.txt`, exactly as the
same rule blocks a read.

Failing that, an unenforceable rule should be **rejected loudly** — a startup error, or a
per-rule warning naming the rule — rather than accepted and ignored. Silently dropping a deny
rule fails open, which is the wrong direction for a security control.

### Error Messages/Logs

```shell

```

### Steps to Reproduce

Each rule is injected on its own via `--settings` so the rules cannot interact, and every probe
writes to the same target in an **existing** directory, so a missing parent cannot confound the
result with `ENOENT`:

```bash
probe() { # probe
claude -p "Run this exact bash command and report only its stdout, verbatim: \
node -e 'try{require(\"fs\").appendFileSync(\"$3\",\"\");console.log(\"WRITABLE\")} \
catch(e){console.log(\"blocked:\"+e.code)}'" \
--allowedTools Bash \
--settings "{\"sandbox\":{\"filesystem\":{\"denyWrite\":[\"$2\"]}}}"
rm -f "$3"
}

T=/workspace/docs/probe.txt # docs/ already exists
probe literal /workspace/docs/probe.txt "$T"
probe mid-star2 '/workspace/**/probe.txt' "$T"
probe mid-star1 '/workspace/*/probe.txt' "$T"
probe trail-star2 '/workspace/docs/**' "$T"
probe bare-star2 '/workspace/**' "$T"
```

The `denyRead` comparison uses the same harness with `denyWrite` swapped for `denyRead`, the
target seeded with `printf 'seed\n' > "$T"` beforehand, and `readFileSync` in place of
`appendFileSync`.

### Results

`sandbox.filesystem.denyWrite`:

| Rule | Target | Result |
|---|---|---|
| `/workspace/docs/probe.txt` | `/workspace/docs/probe.txt` | `blocked:EACCES` ✅ |
| `/workspace/**/probe.txt` | `/workspace/docs/probe.txt` | **`WRITABLE`** ❌ |
| `/workspace/*/probe.txt` | `/workspace/docs/probe.txt` | **`WRITABLE`** ❌ |
| `/workspace/docs/**` | `/workspace/docs/probe.txt` | `blocked:EROFS` ✅ |
| `/workspace/**` | `/workspace/docs/probe.txt` | `blocked:EROFS` ✅ |

`sandbox.filesystem.denyRead`, same rule shapes — **all enforced**:

| Rule | Target | Result |
|---|---|---|
| `/workspace/docs/probe.txt` | `/workspace/docs/probe.txt` | `blocked:EACCES` ✅ |
| `/workspace/**/probe.txt` | `/workspace/docs/probe.txt` | `blocked:EACCES` ✅ |
| `/workspace/*/probe.txt` | `/workspace/docs/probe.txt` | `blocked:EACCES` ✅ |

### Claude Model

Opus

### Is this a regression?

I don't know

### Last Working Version

_No response_

### Claude Code Version

2.1.263

### Platform

Anthropic API

### Operating System

Ubuntu/Debian Linux

### Terminal/Shell

Cursor

### Additional Information

_No response_

Guía de contribución

No hay ninguna guía de contribución indexada para este repositorio

Línea de trabajo

Start by running the provided Linux probe harness with the denyWrite and denyRead rule shapes, then inspect the sandbox filesystem permission handling and the warning shown by /status and claude doctor. Done means mid-path wildcards either block subprocess writes like denyRead does or are rejected with a per-rule warning or startup error.

Escrito por el modelo de indexación a partir del texto del issue.

Evaluación

Stack tecnológico
bash, docker, linux, node.js
Área
cli, operating-systems, security
Tipo de issue
Error
Dificultad
4/5
Tiempo estimado
3-5 días
Estado de actividad
Activo
Claridad
Bastante claro
Aptitud para principiantes
58/100

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.