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 it — pkg/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.
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(viaignore_vcs, on by default). This is a display filter only. Gitignored paths are omitted fromlist_directory,directory_treeandsearch_files_content— butread_fileopens them without complaint. Verified:shouldIgnorePath(pkg/tools/builtin/filesystem/filesystem.go:805) has exactly three callsites, all listing/search;handleReadFile(:978),handleWriteFile(:1372) andhandleEditFile(:897) never consult it. Soread_file secrets.envsucceeds on a gitignored file.allow_list/deny_list. These genuinely block every filesystem operation, with kernel-level*os.Rootenforcement — 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 it —pkg/runtime/toolexec/permissions.gopasses the raw arguments through with no path resolution. A rule forsecrets.envis 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'sgitignore.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.VCSMatcherreturns 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..agentsignoremust 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.gitignoremid-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, inshouldIgnorePath.Independent of
ignore_vcs. Settingignore_vcs: falsedisables.gitignorefiltering; it must not silently un-hide.agentsignoreentries, which are a different intent.Sync with permissions. Derive deny rules from the patterns and merge them into the team checker, so
/permissionsshows 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
.agentsignorefile at the project root, using gitignore syntax:Present ⇒ active. No configuration.
Semantics — matched paths are:
list_directory,directory_tree,search_files_contentread_file,read_multiple_files,write_file,edit_file,create_directory,remove_directoryPaths 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
.agentsignoregoverns the filesystem toolset. An agent with theshelltoolset can stillcat secrets.env:shell.go:192execs the command without inspecting it, and no filesystem guard is consulted. Thelsptoolset 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:.agentsignoreis a strong default that keeps sensitive files out of the agent's view and context, and pairs withpermissionsor sandbox mode when a genuine boundary is needed.