anthropics / anthropics/claude-code

[BUG] No single syntax to express "these are the only files/directories Claude may read" — and every available workaround is worse

Abierto
#92,615 1 comentario 0 reacciones 0 asignados Ver en GitHub
area:permissions area:sandbox enhancement
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?

## What's Wrong?

There is no single expression in `permissions` that says "these are the only readable files/directories." You can only express the inverse — list everything that is *not* allowed — and every practical consequence of that makes it worse, not just inconvenient:

**1. Only the inverse can be expressed.** The natural way anyone would first try to write "only `app/` is readable" is a broad deny plus a narrow allow:
```json
{ "permissions": { "deny": ["Read(/**)"], "allow": ["Read(/app/**)"] } }
```
This does not work. Rules are evaluated in a fixed `deny > ask > allow` order that specificity never changes, so the broad `deny` also blocks the narrower `allow` path — `app/` itself becomes unreadable. The only way to achieve "only `app/` is readable" is to manually write `deny` for every *other* top-level directory and file in the project. For a normal Rails project that's roughly 100 lines to express what should be a single line.

**2. Because that enumeration alone isn't trusted to hold, you also need auxiliary scripts.** The standard advice is that `permissions.deny` can be bypassed (e.g. via `Bash`), so on top of the ~100-line enumeration we also had to write a custom `PreToolUse` hook script that re-checks the resolved path on every file-tool call, plus dependency checks and fail-closed error handling once that hook became load-bearing. Expressing one access boundary now requires a config file *and* a maintained shell script, for something that should be a declarative one-liner.

**3. Adding a single exception later requires rewriting large chunks of existing rules.** Because there's no positive whitelist, you can't "add one more allowed path." To allow one specific file inside an already-denied directory (e.g. allow `config/routes.rb` while still denying the rest of `config/`), you have to delete the directory-wide `deny "Read(/config/**)"` and replace it with an enumerated list of *every other file in `config/` you still want denied*. A one-line exception requires rewriting an entire section of the config.

**4. Newly added files/directories are ambiguous by construction.** Since the whole mechanism is an inverse, hand-maintained list, anything added to the project after the list was written — a new top-level directory, a new root-level file — is readable by default. Nothing flags this. The list doesn't degrade gracefully; it silently stops covering the project as the project evolves.

**5. After all of that — the deny enumeration, the auxiliary hook script, the rewrites for every exception, and remaining vigilant about new files — it still cannot achieve reliable allow/deny.** Any ordinary command that opens a file directly (e.g. `bin/rails runner "puts File.read('config/database.yml')"`, `ruby -e "..."`) bypasses the entire thing, because `Bash` deny rules only match literal command strings, not what file a command will actually touch. There is no version of this that ends in an actual guarantee, no matter how much of the above you do.

### What Should Happen?

## What Should Happen?

There should be one syntax that directly expresses the allowed set, e.g.:
```json
{ "permissions": { "readableRoots": ["app/**"] } }
```
...where anything not matching a listed root is denied by default, including things added later, and adding an exception is adding one entry, not rewriting a section. Ideally this would also apply to `Bash` (a real path-based sandbox), since a string-pattern `deny` can never fully guarantee "no command can read outside this root."

### Error Messages/Logs

```shell
## Error Messages/Logs

(none — this is a missing expressive capability, not a crash or error)
```

### Steps to Reproduce

## Steps to Reproduce

1. Try to express "only `app/` is readable" the natural way — broad `deny` + narrow `allow` (shown above). Confirm it fails: `app/` itself becomes unreadable, because `deny` always wins regardless of specificity.
2. Switch to the only working approach: enumerate every non-`app` top-level directory/file in `deny`. Confirm this takes on the order of 100 lines for a standard Rails project layout.
3. Try to add one exception (e.g. allow `config/routes.rb` while keeping the rest of `config/` denied). Confirm this requires deleting the `config/**` deny entry and re-enumerating every other file in `config/` individually — not adding a single line.
4. Add a new top-level directory to the project that isn't in the `deny` list yet (e.g. `uploads/`). Confirm it's readable by default, with no warning that the restriction no longer covers the whole project.
5. With the full `deny` enumeration from step 2 in place, run an ordinary command like `bin/rails runner "puts File.read('config/database.yml')"`. Confirm it succeeds, bypassing the entire configuration.

### Claude Model

None

### 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

Terminal.app (macOS)

### Additional Information

## Regression

No, this never worked

## Claude Model

Not sure / Multiple models

## Claude Code Version

(please fill in via `claude --version`)

## Platform

(please select — Anthropic API / AWS Bedrock / Google Vertex AI / Other)

## Operating System

Other Linux

## Terminal/Shell

(please fill in)

## Additional Information

Other things checked and ruled out as ways to express this today: `permissions.additionalDirectories` (only adds to the readable scope, can't narrow it), `permissions.defaultMode: "dontAsk"` (still auto-runs "recognized read-only Bash commands," so it doesn't express a whitelist either, and its effect on `Read`/`Grep`/`Glob` isn't documented), and `permissions.blockReadsOutsideWorkingDirectories` (only fences reads outside the whole project, not one subtree of it from another). None of these provide a single construct for "this subtree only," and none of them remove the need for the enumeration, the auxiliary hook, the per-exception rewrites, or the gap against newly added files described above.

Guía de contribución

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

Línea de trabajo

Start with the permissions configuration and the PreToolUse hook behavior described in the report; reproduce the broad deny/narrow allow case and the bin/rails runner bypass. Done means a documented readable-root boundary denies unlisted and newly added paths, supports exceptions without re-enumeration, and has tests covering command-based reads.

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

Evaluación

Stack tecnológico
json, python, shell
Área
cli, security, tooling
Tipo de issue
Nueva funcionalidad
Dificultad
5/5
Tiempo estimado
Más de una semana
Estado de actividad
Activo
Claridad
Bastante claro
Aptitud para principiantes
30/100

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.