anthropics / anthropics/claude-code

[FEATURE] `.agentsignore`: gitignore-syntax file for paths agents must never access

未关闭
#92,816 0 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看
area:permissions area:security enhancement
主要语言
Python
星标
145k
派生
23.1k
PR 合并指标
PR 指标待抓取

描述

### Preflight Checklist

- [x] I have searched [existing requests](https://github.com/anthropics/claude-code/issues?q=is%3Aissue%20label%3Aenhancement) and this feature hasn't been requested yet
- [x] This is a single feature request (not multiple features)

### Problem Statement

Claude Code can already express "never touch this" through `permissions.deny`, and the [permissions docs](https://code.claude.com/docs/en/permissions) confirm `Read` and `Edit` rules use gitignore pattern syntax, resolve `//`, `~/`, `/`, and `./` anchors, and block in every mode including `bypassPermissions`. The capability is real. What is missing is a way to *state* it as an ignore file rather than as Claude-specific JSON permission rules.

Four concrete costs of the settings-only form:

**1. Every path must be written twice.** `Read()` and `Edit()` are separate rule namespaces, so protecting one directory means two entries, and forgetting the second silently leaves writes allowed:

```json
{
"permissions": {
"deny": [
"Read(~/vaults/personal/**)",
"Edit(~/vaults/personal/**)",
"Read(//Users/me/clients/acme/**)",
"Edit(//Users/me/clients/acme/**)"
]
}
}
```

**2. No re-inclusion.** gitignore's defining feature is `!`, and it is exactly what carve-outs need. The docs state a deny rule "can't carry allowlist exceptions," and a narrower `allow` rule does not override a broader `deny`. So "ignore this whole tree except one subdirectory" is not expressible at all:

```gitignore
vaults/personal/
!vaults/personal/work-notes/ # no equivalent today
```

**3. The `/path` anchor is a documented footgun.** A `/`-anchored rule resolves against *the settings source that defines it*, so `Read(/secrets/**)` written in `~/.claude/settings.json` blocks `~/.claude/secrets/**` — not the `secrets/` directory in any project. The docs call this out explicitly, which is a sign the anchoring model is doing more work than an ignore file would need to.

**4. Patterns cannot be colocated or checked in as an ignore file.** They live only in settings.json layers, so the rules travel with the settings file rather than with the tree they describe, and there is no per-directory form the way `.gitignore` has one.

Underneath all four: `.gitignore` answers "what should git not track," which is a genuinely different question from "what should an agent never read." Files are routinely one and not the other in both directions — a gitignored `docs/plans/` the agent should read freely, a checked-in fixture or vendored blob it should skip. There is no file today that answers the second question, and `.claudeignore` — which the model itself sometimes suggests — is not a real feature and appears nowhere in the documentation.

### Proposed Solution

Support an `.agentsignore` file whose contents are gitignore syntax, interpreted as "do not read, search, edit, or write these paths."

**Semantics**

- Same pattern grammar as `.gitignore`, including `!` re-inclusion and last-match-wins ordering. Reusing the grammar wholesale is the point: no new syntax to learn, no per-tool wrapping.
- One entry governs all file access — `Read`, `Edit`, `Write`, `Glob`, `Grep`, `@`-mention pickers, and the Bash file commands `permissions.deny` already recognizes.
- Anchoring follows gitignore's own rule: patterns resolve relative to the directory containing the file. No settings-source indirection.

**Discovery**

- Project: `.agentsignore` at the working-directory root, and in subdirectories, matching how `.gitignore` nests.
- Global: `~/.agentsignore`, applying to every session regardless of cwd — the cross-project layer, expressed as patterns instead of `//` absolute paths.

**Precedence**

Treat it as equivalent in force to a `permissions.deny` rule: it blocks in every permission mode including `bypassPermissions`, and it composes with existing deny rules rather than replacing them. `!` re-inclusion should carve out only within `.agentsignore` itself, so it can never widen access past an explicit `permissions.deny` rule or a managed policy.

**Why this name.** `.gitignore` is scoped to version control and reusing it for agent access control conflates two orthogonal concerns. `AGENTS.md` has become the cross-tool convention for agent instructions; `.agentsignore` extends that convention to access scope, so one file serves every agent CLI a team runs instead of each shipping its own dotfile. A Claude-specific `.claudeignore` would work mechanically but would guarantee that a team running more than one agent maintains N parallel files.

### Alternative Solutions

**`permissions.deny` in `~/.claude/settings.json`** — the current best answer, and what I use. It covers the core need genuinely well: gitignore pattern syntax, cross-project reach via `//` and `~/` anchors, enforcement in every mode. Its limits are the four in the problem statement: duplicate `Read()`/`Edit()` entries, no `!` re-inclusion, settings-source anchoring for `/path`, and no colocated or per-directory file form.

**Sandboxing** — [documented](https://code.claude.com/docs/en/sandboxing) as the OS-level answer, and the only thing that stops arbitrary subprocesses from reading a path. It is a different tool for a different job: it enforces a boundary, it does not express a curated ignore list, and it is coarse where this request wants pattern granularity. `.agentsignore` would not replace it.

**`.gitignore` as a proxy** — rejected. The two questions genuinely diverge, in both directions.

**Prior art in this repository** (none currently open, and none covering an ignore-file form):

- #25606 — `deniedPaths` array in `~/.claude/settings.json`. Closest existing request; closed as inactive after the author pushed back on a duplicate-bot flag, not resolved on merits.
- #63990, #65812, #63967 — `.claudeignore` proposals, all closed. #65812 makes the same `.gitignore`-is-not-access-control argument, scoped to a project-level Claude-specific file.
- #92643 — open, but the inverse model: a default-deny *allowlist* (`restrictFileToolsToDirectories`) for Read/Write/Edit. It reports the same underlying constraint from the other direction — a broad `Deny` defeats a narrower nested `Allow`, so exceptions are inexpressible. `!` support in an ignore file addresses that constraint within the denylist model.

### Priority

Medium - Would be very helpful

### Feature Category

Configuration and settings

### Use Case Example

I run several agent CLIs in the same working tree, and my home directory holds material no agent should read: a personal knowledge vault, client work under NDA in sibling directories, and local credential stores.

**Today**, the global layer is `~/.claude/settings.json`:

```json
{
"permissions": {
"deny": [
"Read(~/vaults/personal/**)",
"Edit(~/vaults/personal/**)",
"Read(~/clients/**)",
"Edit(~/clients/**)",
"Read(//**/*.pem)",
"Edit(//**/*.pem)"
]
}
}
```

This works. Three observations from maintaining it:

1. Six entries for three rules, and the failure mode of dropping an `Edit()` line is silent — the path stays writable.
2. I cannot express the carve-out I actually want. `~/vaults/personal/` holds one subdirectory of work notes I *do* want the agent to read. There is no way to write that, so I either expose the whole vault or enumerate every sibling to exclude — and re-enumerate whenever I add one.
3. The rules are Claude-specific, so the same policy is restated in each other agent's config format, and the three drift.

**With `.agentsignore`**, `~/.agentsignore` — patterns anchored at the file's own directory, so home-relative here:

```gitignore
# Personal vault, except the work notes
vaults/personal/
!vaults/personal/work-notes/

# Client work under NDA
clients/

# Key material anywhere below home
**/*.pem
```

One file, one grammar, and the carve-out on the third line is the case that has no expression today.

**Per-project**, a checked-in `.agentsignore` at a repository root:

```gitignore
# Vendored — large, and never the thing being changed
vendor/
third_party/

# Recorded fixtures: real payloads, scrubbed but still noisy
tests/fixtures/recorded/
!tests/fixtures/recorded/README.md
```

Every one of these is tracked by git, so `.gitignore` cannot express any of it, and today the whole set has to be restated as `Read()`/`Edit()` pairs in `.claude/settings.json`.

### Additional Context

**Interaction with existing configuration.** `.agentsignore` should compose with `permissions.deny` rather than replace it, and never widen access: a `!` line should carve out only within `.agentsignore`'s own matches, so an explicit `permissions.deny` rule or a managed-settings policy still wins. Settings-based rules stay the right home for tool-scoped rules like `Bash(...)`; this file is only about file paths.

**Scope boundary.** Same enforcement surface `permissions.deny` documents today — built-in file tools, recognized Bash file commands, redirection targets. Arbitrary subprocesses that open files themselves remain the [sandbox](https://code.claude.com/docs/en/sandboxing)'s domain. This request does not claim to close that gap.

**Incremental path.** If a new file is more than is wanted, the single highest-value piece is `!` re-inclusion in existing `permissions.deny` patterns. That alone makes "ignore this tree except one subdirectory" expressible, which is the one thing in this request that has no workaround at all — the rest is ergonomics and portability.

**Prior art.** `.gitignore`, `.dockerignore`, `.npmignore`, `.eslintignore`, and `.prettierignore` all establish that a plaintext pattern file colocated with the tree it describes is the ergonomic shape for this problem. `AGENTS.md` establishes the cross-tool naming convention this proposal follows.

**Documentation note, tangential but related.** `.claudeignore` does not appear anywhere in the documentation and does not exist, yet the model recommends it to users unprompted — #16704 documents that confusion, and it recurs across #36163, #51105, #56997, and #86196, where users file bugs against a file that was never a feature. Whatever the outcome here, a stated position on ignore files would stop that class of report.

贡献指南

这个仓库没有索引到贡献指南

调研方向

Start with the permissions documentation and the existing permissions.deny configuration in ~/.claude/settings.json, then trace how file access is enforced across Read, Edit, Write, Glob, Grep, @-mention pickers, and recognized Bash file commands. Define discovery and precedence for project and global .agentsignore files, including gitignore ordering and ! re-inclusion. Done means the documented access surfaces honor the file while explicit deny and managed policies still win; no repository tests or implementation files are named in the issue.

由索引模型根据 Issue 内容生成。

评估

技术栈
git
领域
cli, security
Issue 类型
功能
难度
5/5
预计耗时
一周以上
活跃度
活跃
描述清晰度
基本清楚
新手友好度
35/100

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。