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
- Lingua principale
- Python
- Stelle
- 145k
- Fork
- 23.1k
- Metriche di merge delle PR
- Metriche PR in attesa
Descrizione
### 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.
Guida per i contributori
Nessuna guida per i contributori indicizzata per questo repository
Direzione di ricerca
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.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Valutazione
- Stack tecnologico
- json, python, shell
- Ambito
- cli, security, tooling
- Tipo di issue
- Funzionalità
- Difficoltà
- 5/5
- Tempo stimato
- Più di una settimana
- Stato di attività
- Attiva
- Chiarezza
- Abbastanza chiara
- Idoneità per principianti
- 30/100