Team policy
One repo can be audited by reading it. Forty repos need a written standard that CI checks mechanically — that is what agentdoctor.policy.json is. Commit it at the repo root (or ship the same file to every repo from a central location) and the eight policy/* rules activate automatically. No flag, no account. Repos without a policy file never see these rules fire.
Quick start
agentdoctor --init-policy
writes a starter policy:
{
"requiredDeny": ["Read(./.env*)", "Read(**/.ssh/**)", "Read(**/*.pem)", "Read(**/.aws/credentials)"],
"forbiddenAllow": ["Bash(*)", "Bash(:*)", "Bash()", "WebFetch(*)", "Bash(**sudo**)", "Bash(**rm -rf**)"],
"forbiddenPermissionModes": ["bypassPermissions"],
"allowedMcpServers": [],
"requiredHooks": [],
"maxMemoryTokens": 6000
}
Edit, commit, done. agentdoctor now fails (exit 1) when the repo violates it.
Fields
requiredDeny: string[]
Deny rules every repo must carry, in any settings file. Enforced by policy/missing-required-deny. Deny rules are the only guardrail evaluated before anything runs, which makes them the one thing worth mandating centrally.
forbiddenAllow: string[]
Allow rules no repo may carry. Enforced by policy/forbidden-allow.
allowedMcpServers: string[]
If present, every configured MCP server name must match an entry. Enforced by policy/unapproved-mcp-server. This turns "someone committed a new MCP server" from a silent event into a review decision. Omit the field entirely to skip this check; an empty array means *no servers are approved*.
requiredHooks: string[]
Hook events that must be configured, e.g. ["PreToolUse"] if your org mandates a guardrail hook. Enforced by policy/required-hook-missing.
maxMemoryTokens: number
A ceiling on the estimated token size of the project's always-on memory files (CLAUDE.md et al., user scope excluded). Enforced by policy/memory-budget-exceeded. A context budget is the only thing that stops memory files growing without limit.
forbiddenPermissionModes: string[]
Usually ["bypassPermissions"]. Enforced by policy/forbidden-permission-mode.
Wildcard semantics
Permission rules themselves contain *, so policy patterns treat it literally:
- A single
*is literal."Bash(*)"forbids exactly the ruleBash(*)— it does not forbidBash(npm test:*). **is the wildcard."Bash(**)"matches every Bash rule;"Bash(**sudo**)"matches any Bash rule mentioning sudo.
This is the difference between "nobody may have the blanket rule" and "nobody may run Bash at all" — the starter policy uses both deliberately.
Two rules that need no policy fields
policy/permission-driftfires when.claude/settings.local.jsonadds an *unrestricted* allow rule the committed project config does not grant. Local settings are invisible in code review; this makes the widening visible.policy/file-invalidfires when the policy file itself fails to parse — a policy that silently enforces nothing is the worst state for a guardrail.
Rolling out across an organisation
- Write one policy centrally. Start with
requiredDeny+forbiddenPermissionModesonly — they are the least controversial and catch the worst failure modes. - Ship it to each repo (commit it, or
curlit in CI before running agentdoctor). - Run
agentdoctor --no-userin CI. Use a baseline per repo if there is a backlog. - Tighten over time: add
allowedMcpServersonce you have inventoried what is in use, thenmaxMemoryTokensonce teams have trimmed.
A JSON Schema for the policy file ships with the package (schemas/policy.schema.json) and is served on the docs site, so editors validate it as you type.