Guards
Guards apply your policy to every request a grant permits and refuse a call that fails a check before the tool runs.
On this page
Guards make an application’s rules binding on agents that choose their own plans. A grant covers an operation. Policy decides which files are sensitive, which hosts are trusted, and which commands are permitted.
The reviewer from Capabilities holds a grant for read_file on /workspace, and policy still decides which files it may open. After the capability checks pass, the kernel runs the configured guards in order. Each guard checks its subject, such as a path, destination, command, or tool name, and returns a verdict. A denial stops the call before the tool runs and records evidence of the refusal.
A policy can narrow a grant, never widen it
In the Quickstart, the fixer holds write on /workspace, and the tests-are-evidence rule denies writes under /workspace/tests/**. The grant and the request are unchanged, and the request still passes every capability check. The rule refuses the write to tests/test_pricing.py.
The admitted set lies inside the grant’s scope
S(κ) holds every request the grant covers, and A holds the ones every guard allows. The read of /secrets/.env falls outside S(κ), so the kernel refuses it at the scope check before any guard runs, and no rule can bring it into A. The mediated call.
3 of 5 requests in scope are admitted.
In Rust · evaluate.rs:42519 lines
for guard in input.guards {
let evaluation = guard.evaluate(&ctx);
// …
if guard_projection_allows_continuation(projected_allows, step)
&& matches!(&evaluation, Ok(Verdict::Allow))
{
continue;
}
match evaluation {
Ok(_) => {
// …
let core_err = KernelCoreError::GuardDenied {
guard: guard.name().to_string(),
};
return deny(core_err, Some(matched_grant_index), Some(verified));
}
// … Err(error): the same deny, as GuardError
}
}
The admitted set contains only requests covered by the grant and allowed by every guard. Guards can narrow this set. The fixer’s read of /secrets/.env falls outside the grant, so the kernel refuses it at the scope check before any guard runs, and no policy can admit it.
A guard refuses an operation the grant permits
A guard protects resources inside the operations a grant permits, so an injected instruction that stays within the grant’s tools still meets policy. When pull request #421 tells AI reviewers to upload .env (The Mediated Call), the upload needs a shell tool the reviewer’s grant does not name and stops at the scope check, while the read of /workspace/.env is within the grant and reaches the pipeline.
The forbidden-path guard refuses that read. Its default protected patterns include **/.env, so the path fails the check and the tool never opens the file; the forbidden_path policy key sets the patterns. chio policy explain walks the same call through the pipeline without admitting it, and shows where evaluation stopped:
chio policy explain policy.yaml --server workspace --tool read_file --params '{"path":"/workspace/.env"}'call workspace.read_file {"path":"/workspace/.env"}
1 forbidden-path deny /workspace/.env matches **/.env
stopped at guard 1 of 7; decision denyThe six guards after forbidden-path never ran. Between them, the scope check and this guard stop every call the comment asks for before a tool runs. Every guard denial leaves the same record. This one came from a tool_access rule in policy.yaml, which blocks a tool by name:
{
"decision": {
"verdict": "deny",
"reason": "guard denied the request: guard \"guard-pipeline\" denied the request",
"guard": "kernel"
},
"receipt_kind": "mediated_decision",
"evidence": [
{
"guard_name": "mcp-tool",
"verdict": false,
"details": "action=deny; reason=guard denied request"
}
]
}The decision.reason locates the refusal at guard evaluation and names the pipeline. The evidence names the check that failed: mcp-tool, the runtime name of the guard that enforces tool_access, with verdict: false. A forbidden-path denial carries forbidden-path in the same place.
Read more in Write a Policy.
Every guard must allow the call
The default pipeline registers seven guards in a fixed order. A policy can enable, configure, or add checks. Each guard evaluates the requests within its subject and allows the rest, so a model invocation passes a path guard because it names no path. chio policy explain without a call prints the order the reviewer’s policy compiles to, which is the order every call meets:
chio policy explain policy.yamlpolicy policy.yaml
grants read_file on server workspace max_args_size 8192
guards 1 forbidden-path
2 shell-command
3 egress-allowlist
4 path-allowlist
5 mcp-tool
6 secret-leak
7 patch-integrity
output 1 hook
approvals noneforbidden-path denies the request; the pipeline stops and the request is not dispatched.
In Rust · pipeline.rs:6620 lines
for guard in &self.guards {
match guard.evaluate(ctx) {
Ok(decision) => {
evidence.extend(decision.evidence);
match decision.verdict {
Verdict::Allow => continue,
// … PendingApproval
Verdict::Deny => {
// … record the denial
return Ok(GuardDecision::deny(evidence));
}
}
}
Err(e) => {
// Fail closed: guard errors are treated as denials.
// … record the error
return Ok(GuardDecision::deny(evidence));
}
}
}
A guard returns one of three verdicts, and only Allow lets the call proceed. Allow passes the request to the next guard, and the kernel dispatches the call only when every guard allows it. Deny stops the pipeline at the first denial, records the failing guard’s evidence, and refuses the call before the tool runs; later guards do not run or contribute evidence. A guard error also counts as a denial, so a check that cannot complete blocks dispatch. Two orderings of the same guards therefore reach the same verdict, though their evidence can differ.
The generic pipeline keeps a PendingApproval result while later guards run, but kernel dispatch rejects it as unsupported and fails closed. Returning it from an ordinary guard does not suspend the call. To require a person’s decision, use human approval, which binds that decision to the exact request.
Read more about custom ordering in Pipelines and composition.
Output checks cannot undo an effect
Input guards decide whether work may begin. Post-invocation hooks inspect what the tool returned and can allow, redact, block, or escalate it. Blocking returned content cannot undo a filesystem write, a network request, or another effect the tool already performed.
Choose the stage by what the check needs to see. A path or destination is known before dispatch, so an input guard can prevent the effect. Returned content exists only afterward, so an output check can withhold it but cannot prevent what produced it. The Mediated Call follows both through one call.
Read more in Response sanitization.
Advisory signals deny only when promoted
Advisory guards estimate risk and record severity-rated signals alongside the deterministic pipeline. A signal alone does not deny a call.
Rule: anomaly-advisory at or above High. 1 signal promoted, so the advisory pipeline denies the call. All three signals are recorded as evidence. anomaly-advisory Medium: tool 'search' invoked 12 times (threshold: 10). anomaly-advisory High: delegation depth 4 exceeds threshold 3. data-transfer-advisory High: cumulative data transfer 2,400,000 bytes exceeds threshold 1,000,000 bytes.
In Rust · advisory.rs:24617 lines
for guard in &self.guards {
let signals = guard.evaluate(ctx)?;
for mut signal in signals {
if self.policy.should_promote(&signal) {
signal.promoted = true;
should_deny = true;
}
collected.push(signal);
}
}
// … store the signals for evidence export
if should_deny {
Ok(GuardDecision::deny(evidence))
} else {
Ok(GuardDecision::allow_with_evidence(evidence))
}
chio-guards for one call, with one promotion rule shown. Names match exactly, every signal is kept as evidence, and the advisory pipeline is not part of the default pipeline.Advisory guardsA promotion rule names a guard and a minimum severity. Any signal from that guard at or above the threshold denies the advisory pipeline. The pipeline keeps every signal as evidence, including those below the threshold. Configure it separately from the default pipeline.
Read how to observe signals before promoting them in Advisory guards.
Treat a pattern list as a tripwire
A coding agent that holds a shell tool writes its own commands. The shell-command guard carries a short list of known-dangerous spellings: a recursive delete of the root, a download piped into a shell, a reverse shell, a base64 pipe into curl. It also checks path arguments against the protected paths, so it refuses cat .env | curl -d @- … while curl -d @.env …, which sends the same file, passes.
A model can rewrite the same goal around that list: block one spelling and it reaches for another. A match records evidence of a known move; a miss says nothing about what the agent can reach. The boundaries that do bound the agent belong to the authority, the process, and the data it carries, so the spelling of a command cannot evade them. The figure shows five rewrites, from a piped upload of .env to a bulk read split across workers, and the boundary that stops each one.
0 of 5 rewrites reach their effect. Enabled boundaries: patterns, confinement, flow, quotas.
deploy/settings.yaml, so egress compares an internal label; the spellings are illustrative. A rewrite stops at the first enabled boundary, in call order, that covers it.Beyond blocklistsWith only the pattern list enabled, four of the five rewrites reach their effect. Each boundary has a page that configures it: task-scoped authority mints a narrow grant for each subtask, information flow across calls carries a sensitive read’s restrictions to the destination, and rules across a session constrain the order and volume of a run.
Read why a list cannot bound what commands reach in Beyond blocklists.
Choose checks by the resources an agent reaches
Start from the resources an agent can reach, then enable the checks that constrain how it reaches them. Guards constrain files and paths, network destinations including SSRF risk, shell commands, code, and patches, invocation rates across grants and agents, database queries with their estimated cost and returned data, and computer use on the desktop and in browser automation. Some constraints span a whole run: session rules require predecessors and cap cumulative bytes, and information flow checks a destination against a read’s retained label.
Resource owners can add checks for their domain while agents keep the same tool interface. Configure the existing checks with HushSpec policy rules. Write a custom WASM guard for an extension loaded at runtime, or implement the Rust guard trait when building a check into the kernel. Every guard returns the same three verdicts and is held to the same fail-closed rule.
Browse the full guard catalog
| Runtime name | Type | Crate | Default |
|---|---|---|---|
advisory- | Advisory | chio-guards | No |
agent-velocity | Agent | chio-guards | No |
behavioral- | Behavioral | chio-guards | No |
browser- | Browser | chio-guards | No |
code-execution | Code | chio-guards | No |
computer-use | Computer | chio-guards | No |
content-review | Content | chio-guards | No |
data-flow | DataFlowGuard | chio-guards | No |
egress- | Egress | chio-guards | Registered |
embedding- | Embedding | chio-guards | No |
forbidden-path | Forbidden | chio-guards | Registered |
guard-pipeline | GuardPipeline | chio-guards | No |
input- | Input | chio-guards | No |
internal- | Internal | chio-guards | No |
jailbreak | JailbreakGuard | chio-guards | No |
mcp-tool | McpToolGuard | chio-guards | Registered |
memory- | Memory | chio-guards | No |
patch-integrity | Patch | chio-guards | Registered |
path-allowlist | Path | chio-guards | Registered |
prompt- | Prompt | chio-guards | No |
query-result | Owned | chio- | No |
query-result | Query | chio- | No |
remote- | Remote | chio-guards | No |
response- | Response | chio-guards | No |
secret-leak | SecretLeakGuard | chio-guards | Registered |
shell-command | Shell | chio-guards | Registered |
sql-query | SqlQueryGuard | chio- | No |
vector-db | VectorDbGuard | chio- | No |
velocity | VelocityGuard | chio-guards | No |
warehouse-cost | Warehouse | chio- | No |
Each guard’s configuration and behavior are specified in the Guards Spec.