Access Guard
Mark the paths an AI agent must never read, or never write β and have every C3 surface enforce it. Two glob lists in your project (or global) config, one evaluator, refusals that tell the agent exactly why and what to do instead.
What this is β and is not. Access Guard protects a cooperative agent against mistakes and prompt-injection: an agent that names a protected path gets a firm, reasoned refusal at every C3 surface. It is not a sandbox β it governs file actions invoked through C3's tools and hooks, but does not isolate raw OS processes or external editors. See Coverage for exactly where each layer holds.
Quick start
Add a rule
UI β project β Access Guard tab β Add rule, or:
c3 access add "secrets/**" --kind deny
Test a path
Use the tab's Test path probe, or:
c3 access check secrets/key.txt
Let it work
Agent attempts on protected paths return
[c3-access:denied] refusals that name the rule and tell
the agent not to retry.
Rules & semantics
Rules live in the access section of
.c3/config.json (project scope) and
~/.c3/config.json (global scope):
{
"access": {
"deny": ["secrets/**", "*.pem"],
"read_only": ["docs/legal/**", "migrations/**"]
}
}
| Rule kind | Read | Write / create / delete | Appears in search? |
|---|---|---|---|
| deny | β | β | β (never listed or hinted) |
| read_only | β (unless another rule denies) | β | β |
- Tighten-only. There is no
allowlist. Scopes merge as a union β a rule anywhere applies everywhere. A project config can only add protection, never grant access; an unknown key in the section is a hard config error. - Globs are POSIX-style, case-insensitive.
**crosses directories,*does not; a pattern without/matches the file name at any depth (*.pemmatchesa/b/server.pem). - deny also means deny-create. Path manipulation and OS aliases (short names, trailing dots/spaces, stream syntax) are normalized before matching, so alternate spellings of a protected name cannot slip past the rule.
- deny also means deny-enumerate. Search, maps, and
listings never reveal a denied file's existence. When any rules are
active, search output carries a
[c3-access:limited]footer so the agent doesn't conclude "missing" means "doesn't exist". - Rule text is not secret. Refusals and
c3_statusshow the matched glob β the guard protects file contents and existence, not the rule list. Name globs accordingly. - All rule changes are human-only β UI or CLI, and every mutation is logged to the project's edit ledger. Agents have no tool that can add, remove, or loosen rules.
- Local-only in v1.
.c3/is gitignored: rules do not travel with a cloned repo.
Built-in protections (always on)
These apply in every C3 project, with no configuration, and cannot be overridden. If the access config is corrupt or unparseable, that scope fails closed β everything in it is denied until fixed.
| Paths | Protection | Why |
|---|---|---|
| **/.env* | deny | Secrets never enter agent context |
| .c3/secrets.enc Β· .c3/cred_state.json | deny | Credential vault sidecars |
| .c3/** Β· ~/.c3/** | write-deny | Config self-tampering (incl. access rules + credential registry) |
| .claude/settings*.json | write-deny | Hook/permission self-modification |
| .git/** | write-deny | git hooks are a code-execution vector (reads stay open) |
| C3 install directory | write-deny | Agent self-modification (installed layouts only) |
| *.pem Β· id_rsa* Β· *.key | default rule | Seeded in global scope β visible and removable, unlike builtins |
Refusal messages
Every denial carries a stable machine tag, the matched rule and scope, and explicit no-retry guidance β so the agent pivots instead of retry-looping or silently recreating "missing" files.
| Tag | Meaning |
|---|---|
| [c3-access:denied] | Operation refused by a deny rule (or a write refused where read_only applies via hooks). Policy, not an error β the agent is told not to retry or route around it. |
| [c3-access:read_only] | Write refused; reads remain separately evaluated. |
| [c3-access:limited] | Search/listing footer: results may omit protected paths β a file that doesn't appear may still exist. |
| [c3-access:error] | The enforcement hook itself failed β native write tools are denied until it loads (fail-closed). See Troubleshooting. |
Coverage β honest limits
Enforced: C3 MCP tools (any agent that uses C3 β
Claude Code, Codex, Gemini, Oracle-connected models) Β· Claude Code
native tools (PreToolUse hooks, fail-closed) Β· c3_shell
(best-effort command scan, advisory).
Not enforced: a non-Claude agent's raw shell or direct file APIs β including native file tools in non-Claude CLI environments unless they route through C3 tools β external editors, and OS-level access. Renames/moves via shell commands are a known v1 gap. Access Guard is a guardrail for cooperative agents β pair it with OS permissions when you need a real boundary.
CLI commands
c3 access list # rules by scope, builtins included
c3 access add "secrets/**" --kind deny # project scope
c3 access add "*.pem" --kind deny --global # global scope
c3 access remove "secrets/**" --kind deny
c3 access check secrets/key.txt # probe: verdict + matched rule
c3 access check docs/x.md --op write
UI tab & probe
Each project's UI gains an Access Guard tab: rules grouped by scope (builtins shown locked), an add-rule form, delete with typed confirmation for deny rules, and a Test path probe that shows the verdict, matched rule, and the exact refusal the agent would see. The coverage matrix above is pinned at the bottom of the tab.
Troubleshooting
| [c3-access:error] on every write | The access hook failed to load β this is fail-closed by design.
Check .c3/hook_errors.log, rerun
c3 install-mcp. C3 MCP tools keep working
meanwhile. |
| Everything in a scope denied | That scope's access section is corrupt or has an
unknown key. Fix the JSON; c3 access list names the
scope. |
| Expected file "missing" in search | Check c3 access check <path> β deny rules
remove files from search entirely (deny-enumerate). |
| Rule not matching | Globs are POSIX-style: use forward slashes; remember
* does not cross directories β use **.
Probe with c3 access check. |
Relationship to the Credential Vault. Credentials protect values (never in agent context, injected at the subprocess boundary); Access Guard protects files and folders. The vault's registry and sidecars are covered by Access Guard builtins, and both systems log every mutation to the same edit ledger.