Ship It With AI Mihai Cvasnievschi

Guardrails in five layers

7 min read

By the end you can score a repository on five layers of control and name the one fix that closes its biggest gap.

TL;DR


What are the five layers?

A prompt is a request. A control is something the agent cannot talk its way past.

PocketOS (the Prologue) had a natural-language instruction, which is weaker than layer one. Three lessons get quoted from it, and all three are wrong:

Underneath all five sits least privilege: at every level - the agent, its process, its credentials, its network - grant the minimum that lets the work happen.

The five governance layers stacked as defense in depth LAYER 5Telemetrydetective LAYER 4Security hooksper-action enforcement LAYER 3Secretsstructural protection LAYER 2SandboxOS-level isolation LAYER 1Permissionsallow / ask / deny
Least privilege is the spine. Each layer catches what the others miss.
Figure: The five governance layers, layered as defense in depth.
Layer Blocks Fails when Would it have stopped PocketOS?
1. Permissions Actions the rules or classifier deny Injection talks the agent into requests the gate never sees Could have, if a rule gated destructive calls
2. Sandbox What the kernel refuses: paths, hosts Never switched on, or has a bug Could have, whatever the agent decided
3. Secrets The agent holding a credential A secret sits within reach Could have: no production token in the session
4. Hooks Dangerous categories, before they run A category has no rule Could have: forced human approval
5. Telemetry Nothing; it detects afterwards Nobody is alerted No, but "two days until we notice" becomes "two minutes until the on-call engineer is paged"

No single layer would have saved PocketOS with certainty; any one enforceable layer could have broken the chain. A second 2026 incident, at DataTalks.Club, was the mirror image: Claude Code ran terraform destroy against a stale state file. Same cure: confirmation on destructive operations, only the credentials the task needs, and state the team has verified.


Layer 1: Permissions

Permissions answer one question: is this agent allowed to do this, right now?

I recommend a classifier that makes the allow, ask or deny call on each action, with a small set of explicit rules on top, such as denying writes to .env files. Ten to fifteen overrides per repository is a sound ceiling; past that, your AGENTS.md is too thin, not your rule list too short.

Every team starts here, and a successful prompt injection can defeat it. Necessary, not sufficient.


Layer 2: Sandbox

The kernel does not listen to the agent's reasoning, so injection cannot talk its way past it.

For most teams the default is to read the whole repository, write only inside the worktree, reach only allowlisted hosts and block everything else.

Calibrate by friction: if the agent hits the limits often in normal work, widen them. It costs an afternoon to configure, and some agents ship it opt-in, so check yours (Chapter 1).


Layer 3: Secrets

The safest credential is the one the session never held.

Do not commit .env files, and remember that agents can pick them up from the working directory unasked. Keep secrets in a vault and pull them at runtime through an explicit fetch step the agent can be configured to skip. Put sensitive paths on the sandbox deny-read list: the agent literally cannot read them, whatever the system prompt says.


Layer 4: Hooks

Hooks are the precision instrument: a programmable gate for the categories your team cares about.

A hook is code that runs before the agent executes a given kind of action (pre-tool-use) and, against your rules, allows it, asks for confirmation or blocks it. Off-the-shelf plugins ship a baseline: secret leaks in diffs, SQL injection patterns, hardcoded credentials.

Your own rules go on top: any change to currency-conversion logic needs explicit approval; any commit touching the customer table needs a security reviewer. Reach for a hook when permissions are too coarse and the sandbox too blunt.


Layer 5: Telemetry

The first four layers prevent; telemetry detects, so a failed prevention costs minutes instead of days.

Log every tool call - each Bash command, file write and external API request, with arguments and results - to a central store the security team owns. Then "which sessions touched the payments service in the last thirty days" is a query, not a grep through eighty engineers' local histories. Watch for:


Injection arrives through the work itself

Treat everything the agent reads as untrusted text, and do not count on the agent to spot the attack.

Anywhere the agent reads natural language is a surface: a Jira ticket whose acceptance criteria say "ignore all previous instructions"; an outside contributor's PR comment with an instruction hidden in a review note; an error message from a flaky third-party test; a harmless-looking repository whose planted error nudges the agent to run a "fix" that pulls in its real payload. I have watched an agent follow an instruction buried in a pasted log file because the operator did not think of a log file as untrusted input.

Constrain what the agent can do regardless of what it has read. The sandbox catches the dangerous action even when the agent is convinced it is legitimate; the hook catches the dangerous category even when "just this once" sounds fine. A poisoned AGENTS.md can change the agent on every session start, one more reason it is reviewed like code (Chapter 6).


Beyond the five

Two ordinary practices catch what the layers do not.


In the loop

The rigor lives in the layers below execute; execute just runs the work.

In Chapter 4's execute phase every tool call passes the permission gate, every Bash command the hooks, every file write the sandbox. A blocked action goes back to the orchestrator to decide.


When it breaks


Do this Monday

Score each layer DEFAULT, CONFIGURED, ENFORCED or MONITORED. DEFAULT or CONFIGURED across the board is normal; ENFORCED or MONITORED everywhere is production-grade.


Go deeper