Layered Controls
Layered controls stack from the outermost boundary (platform-level enforcement) to the innermost (runtime guardrails). No single layer is sufficient; together they form defense in depth.
Why controls matter
When agents operate autonomously, the failure modes are different from
human development. An agent does not get tired, but it also does not pause
to question whether an instruction makes sense. It will merge a PR if told
to. It will push directly to main if the path is open. It will commit
.env files if nothing stops it.
Layer 1 — Platform enforcement
Enforced by GitHub. Cannot be bypassed by agents or by code.
- Branch protection on
main. Requires PRs and passing checks. Direct pushes blocked. This is the single most important control — everything else can be worked around if an agent can push directly. - Account separation. Human (admin, merge), Worker bot (commits, PRs), Reviewer bot (reviews only). No bot can both write and approve code.
Layer 2 — Claude Code permissions
Tool-level access control in .claude/settings.template.json.
"deny": [
"Read(.env)",
"Read(.env.*)",
"Read(*.key)",
"Read(*credentials*)",
"Read(*secret*)",
"Write(.env)",
"Write(.env.*)",
"Bash(gh pr merge:*)"
]The merge prohibition is the hard enforcement behind the “only humans
merge” rule. Even if an agent’s instructions say “merge”, the platform
blocks gh pr merge at the tool layer.
Layer 3 — Workflow conventions
Ticket format, PR templates, commit conventions, pre-push hooks. Soft controls — an agent could in principle ignore them, but they shape behavior strongly and surface deviations immediately.
Defense in depth
No layer is sufficient alone. Branch protection without
account separation means one compromised token wins.
Account separation without permission boundaries means a worker can read
your .env. Conventions without platform enforcement are theatre. Together
the layers create overlapping safety zones with no single point of failure.