Skip to content

Add .agentsignore so a project can keep files out of the agent's reach #3742

Description

@dwin-gharibi

Overview

There is no way to tell an agent "never touch these files".

A repository usually contains things the agent has no business reading: .env, *.pem, ~/.aws-style credential directories, customer fixtures, licensed data. Today the only options are:

  • .gitignore (via ignore_vcs, on by default). This is a display filter only. Gitignored paths are omitted from list_directory, directory_tree and search_files_content — but read_file opens them without complaint. Verified: shouldIgnorePath (pkg/tools/builtin/filesystem/filesystem.go:805) has exactly three callsites, all listing/search; handleReadFile (:978), handleWriteFile (:1372) and handleEditFile (:897) never consult it. So read_file secrets.env succeeds on a gitignored file.
  • allow_list / deny_list. These genuinely block every filesystem operation, with kernel-level *os.Root enforcement — but they take literal directory roots only. No globs: vendor/** is treated as a directory literally named **. You cannot say *.pem, and you cannot ignore a single file.
  • permissions. Glob-capable, but rules match the argument string exactly as the model wrote itpkg/runtime/toolexec/permissions.go passes the raw arguments through with no path resolution. A rule for secrets.env is defeated by ./secrets.env.

So: the mechanism with the right syntax can't enforce, and the mechanism that enforces has the wrong syntax.

Meanwhile every adjacent tool has solved this with one file — .gitignore, .dockerignore, .npmignore, .eslintignore. Users already know the syntax and reasonably expect it to exist.

Motivation

Syntax must be gitignore, not an approximation. Parse with the same library git uses (go-git's gitignore.ParsePattern), so ! negation, **, trailing-slash directories, leading-slash anchoring and escaping all behave identically. Users should never have to learn a dialect.

No git repository required. The existing fsx.VCSMatcher returns nothing outside a repo (pkg/fsx/vcs.go:33), which would silently disable the feature for non-git projects — exactly the throwaway scratch directories where secrets are most likely to sit unprotected. .agentsignore must stand alone.

Not cached process-globally. The VCS matcher caches by repo root forever with no invalidation (pkg/fsx/vcs.go:22-27), so editing .gitignore mid-session has no effect. A per-toolset matcher avoids inheriting that.

Enforce at resolveAndCheckPath. Every path-taking handler in the filesystem toolset already funnels through it (filesystem.go:635), so one check covers read/write/edit/mkdir/rmdir. Listing suppression needs a second touch, in shouldIgnorePath.

Independent of ignore_vcs. Setting ignore_vcs: false disables .gitignore filtering; it must not silently un-hide .agentsignore entries, which are a different intent.

Sync with permissions. Derive deny rules from the patterns and merge them into the team checker, so /permissions shows that a project ignores files and an obvious direct attempt is refused early. This is defence in depth, explicitly not the boundary — see below.

Use cases

No response

Proposed solution

A .agentsignore file at the project root, using gitignore syntax:

.env
.env.*
*.pem
*.key
!public.key
.aws/
build/

Present ⇒ active. No configuration.

Semantics — matched paths are:

absent from list_directory, directory_tree, search_files_content
refused for read_file, read_multiple_files, write_file, edit_file, create_directory, remove_directory

Paths are resolved (symlinks, ./, ..) before matching, so an ignored file cannot be reached by spelling it differently — the thing the permissions layer cannot do.

The file hides itself: it names exactly what is being kept back, so serving it to the agent would be a map of what to look for.

Alternatives

No response

Related issues

No response

Additional context

.agentsignore governs the filesystem toolset. An agent with the shell toolset can still cat secrets.env: shell.go:192 execs the command without inspecting it, and no filesystem guard is consulted. The lsp toolset reads and writes files directly too (lsp.go:1405, :1426).

Closing those would mean either command inspection (brittle, trivially evaded by base64/xxd/a heredoc) or real sandboxing — which the project already offers separately. The right framing is: .agentsignore is a strong default that keeps sensitive files out of the agent's view and context, and pairs with permissions or sandbox mode when a genuine boundary is needed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/securityAuthentication, authorization, secrets, vulnerabilitiesarea/toolsFor features/issues/fixes related to the usage of built-in and MCP tools

    Fields

    No fields configured for Enhancement.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions