Guards Spec
The conformance reference for the Chio guard taxonomy: pipeline ordering, fail-closed semantics, verdicts, evidence, advisory promotion, post-invocation hooks, WASM custom guards, and the session journal contract that session-aware guards read from.
Source
This page normatively reflects spec/GUARDS.md in the chio repository. Status: Normative. Version 1.0. The keywords MUST, SHOULD, and MAY are normative per RFC 2119.
Narrative coverage
Guard Pipeline Overview
The Chio runtime kernel evaluates guards in a sequential pipeline before admitting any tool invocation. Guards produce GuardEvidence entries that attach to the signed receipt for the invocation, providing an auditable record of which guards evaluated the request and what they observed.
Guard Categories
| Category | State | Phase | Blocking |
|---|---|---|---|
| Stateless deterministic | None | Pre-invocation | Yes |
| Session-aware deterministic | Session journal | Pre-invocation | Yes |
| Post-invocation hooks | Tool response | Post-invocation | Yes |
| Advisory signals | Session journal (optional) | Pre-invocation | No (unless promoted) |
| WASM custom guards | Sandboxed runtime | Pre-invocation | Configurable |
Evaluation Order
Guards SHOULD be evaluated in the following order within the pipeline:
- Stateless deterministic guards (cheapest, no I/O).
- Session-aware deterministic guards (require journal read).
- WASM custom guards (sandboxed execution, potentially expensive).
- Advisory pipeline (non-blocking signals, evaluated last for observability).
Post-invocation hooks run after the tool produces a response but before delivery to the agent.
Fail-Closed Semantics
The pipeline operates under a universal fail-closed invariant:
- If any guard returns
Deny, the pipeline MUST short-circuit and deny the request. - If any guard returns an error (including internal panics, lock poisoning, or serialization failures), the pipeline MUST treat the request as denied.
- Only when every guard in the pipeline returns
Allowis the request admitted.
Per-guard fail-closed obligations are listed under each guard below. See guard-platform: fail-closed for narrative discussion.
Verdict
Guards return one of three verdicts at the pipeline level. The spec-level verdict vocabulary is Allow, Deny, and PendingApproval.
| Verdict | Effect |
|---|---|
Allow | Admit the request to the next pipeline stage; if all guards allow, dispatch to the tool server. |
Deny | Short-circuit the pipeline. The kernel emits a signed denial receipt and returns the deny envelope. |
PendingApproval | The request requires human-in-the-loop approval before admission. The kernel records the pending state on the receipt; resolution is out-of-band. |
Evidence Accumulation
Guards emit GuardEvidence entries that the kernel attaches to the signed receipt. The accumulated evidence is the auditable trail that lets operators and downstream consumers see which guards evaluated the request and what they observed.
GuardOutput
The GuardOutput type provides a unified representation of guard results in the evidence array. The type discriminator field uses snake_case in JSON serialization.
| Variant | Tag | Fields | Description |
|---|---|---|---|
Deterministic | "deterministic" | guard_name, verdict (bool), details | Result from a standard guard |
Advisory | "advisory" | All AdvisorySignal fields | Non-blocking observation |
Stateless Deterministic Guards
Stateless deterministic guards inspect only the current request context (tool name, arguments, agent identity, capability scope). They require no external state, no session history, and no I/O. Their verdicts are reproducible given the same inputs.
InternalNetworkGuard
Guard name: internal-network. Prevents Server-Side Request Forgery (SSRF) by blocking network egress to private, reserved, and cloud infrastructure addresses. Critical for the HTTP substrate where agents may attempt to reach internal services through tool invocations.
Blocked address classes:
| Class | Range | Rationale |
|---|---|---|
| RFC 1918 (Class A) | 10.0.0.0/8 | Private networks |
| RFC 1918 (Class B) | 172.16.0.0/12 | Private networks |
| RFC 1918 (Class C) | 192.168.0.0/16 | Private networks |
| Loopback (IPv4) | 127.0.0.0/8 | Host-local services |
| Loopback (IPv6) | ::1 | Host-local services |
| Link-local (IPv4) | 169.254.0.0/16 | Auto-configured addresses |
| Link-local (IPv6) | fe80::/10 | Neighbor discovery scope |
| Unique local (IPv6) | fc00::/7 | Private IPv6 ranges |
| Cloud metadata | 169.254.169.254, metadata.google.internal, metadata.azure.com | Cloud instance credentials |
| Kubernetes | kubernetes.default.svc, kubernetes.default | Cluster-internal APIs |
| Broadcast | 255.255.255.255 | Network broadcast |
| Current network | 0.0.0.0/8 | Ambiguous origin |
| IPv4-mapped IPv6 | ::ffff:<private-v4> | Bypass via address format |
DNS rebinding detection blocks hostnames that embed private IP patterns using dash or dot separators (e.g., evil.127-0-0-1.attacker.com). Encoded IP detection blocks hostnames that appear to be obfuscated IP addresses in hexadecimal, decimal, or octal notation.
Fail-closed
NetworkEgress actions.AgentVelocityGuard
Guard name: agent-velocity. Enforces per-agent and per-session rate limits using token-bucket semantics. Unlike the grant-scoped VelocityGuard, this guard rate-limits by agent identity across all capabilities, preventing a single agent from overwhelming the system regardless of how many capabilities it holds. Token-bucket arithmetic uses integer milli-tokens to avoid floating-point drift.
| Bucket | Key | Purpose |
|---|---|---|
| Per-agent | agent_id | Cross-capability rate limit for a single agent |
| Per-session | (agent_id, capability_id) | Rate limit within a single session/capability context |
When both limits are configured, both MUST pass for the request to be allowed; the stricter limit takes effect. If the internal mutex is poisoned, the guard MUST return KernelError::Internal, which the pipeline treats as a denial.
Session-Aware Deterministic Guards
Session-aware deterministic guards consult the session journal to make decisions based on cumulative session history. They require an Chio<SessionJournal> reference and produce deterministic verdicts given the same journal state.
DataFlowGuard
Guard name: data-flow. Enforces cumulative data transfer limits per session, preventing data exfiltration through many small requests that individually appear benign but cumulatively transfer large volumes. The guard reads cumulative bytes_read and bytes_written from the session journal's CumulativeDataFlow snapshot. Limits are checked against running totals, not per-request deltas.
| Option | Type | Default | Description |
|---|---|---|---|
max_bytes_read | u64 or null | null (unlimited) | Maximum cumulative bytes read per session |
max_bytes_written | u64 or null | null (unlimited) | Maximum cumulative bytes written per session |
max_bytes_total | u64 or null | null (unlimited) | Maximum cumulative bytes (read + written) per session |
If the session journal is unavailable (lock poisoned, I/O error), the guard MUST return an error, which the pipeline treats as a denial. The guard MUST NOT default to allow when journal state is inaccessible.
BehavioralSequenceGuard
Guard name: behavioral-sequence. Enforces tool ordering policies to prevent dangerous sequences. The guard reads the tool invocation sequence from the session journal and checks four constraint types:
| Constraint | Description |
|---|---|
| Required predecessors | Tool X MUST NOT run unless tools Y and Z have been invoked earlier in the session |
| Forbidden transitions | Tool X MUST NOT run immediately after tool Y |
| Max consecutive | The same tool MUST NOT run more than N times consecutively |
| Required first tool | The first tool in a session MUST match a specified name |
If the session journal is unavailable, the guard MUST return an error, which the pipeline treats as a denial.
Post-Invocation Hooks
Post-invocation hooks run after a tool produces a response but before that response is delivered to the agent. They inspect response content and can modify, block, or escalate it.
PostInvocationPipeline
The pipeline evaluates hooks in registration order. Each hook returns one of four verdicts:
| Verdict | Effect |
|---|---|
Allow | Response passes through unmodified |
Block(reason) | Response is replaced with an error message; pipeline short-circuits |
Redact(value) | Response content is replaced with the redacted version; subsequent hooks see the redacted version |
Escalate(message) | Response is delivered, but an escalation signal is emitted for operator review |
Pipeline semantics:
- A
Blockfrom any hook MUST stop the pipeline immediately. No subsequent hooks run. - A
Redactreplaces the response for all subsequent hooks. MultipleRedacthooks compose sequentially. Escalatemessages are collected throughout the pipeline and reported alongside the final verdict.- If no hooks modify the response, the final verdict is
Allow.
ResponseSanitizationGuard
Guard name: response-sanitization. Scans tool responses (and request arguments when used pre-invocation) for PII and PHI patterns, then blocks or redacts matches before the data reaches the agent.
| Pattern | Example | Sensitivity | Redaction |
|---|---|---|---|
| SSN | 123-45-6789 | High | [SSN REDACTED] |
user@example.com | Medium | [EMAIL REDACTED] | |
| Phone | (555) 123-4567 | Low | [PHONE REDACTED] |
| Credit card | 4111-1111-1111-1111 | High | [CARD REDACTED] |
| Date of birth | 1990-01-15 or 01/15/1990 | Low | [DATE REDACTED] |
| MRN | MRN: 123456789 | High | [MRN REDACTED] |
| ICD-10 | J18.9, E11 | Medium | [ICD REDACTED] |
The min_level configuration controls the minimum sensitivity threshold; only patterns at or above the threshold trigger the guard. Operators MAY define custom patterns via build_pattern() with name, regex, sensitivity level, and redaction string. Invalid regexes are silently skipped at load time.
Dual-phase operation:
- Pre-invocation: scans request arguments for PII that should not be sent to tool servers; denies if patterns are found.
- Post-invocation:
scan_responsereturnsScanResult::Clean,ScanResult::Blocked, orScanResult::Redacted.
Advisory Pipeline and Promotion
Advisory signals are non-blocking observations emitted during guard evaluation. They provide operators with visibility into request patterns without affecting the verdict, unless explicitly promoted.
AdvisorySignal
| Field | Type | Description |
|---|---|---|
guard_name | string | Name of the advisory guard that produced the signal |
description | string | Human-readable observation |
severity | AdvisorySeverity | Severity classification |
metadata | object or null | Structured metadata about the observation |
promoted | boolean | Whether this signal was promoted to a deterministic denial (set by the promotion policy, not the guard) |
Advisory signals are serialized and attached to the receipt as part of the GuardOutput evidence array. They MUST be included in the signed receipt body so that auditors can review all observations.
Severity Levels
| Level | Ordinal | Meaning |
|---|---|---|
Info | 0 | Informational observation, no action needed |
Low | 1 | Worth monitoring over time |
Medium | 2 | May warrant investigation |
High | 3 | Likely needs operator attention |
Critical | 4 | Strong signal of abuse or anomaly |
Severity levels are ordered. A promotion rule with min_severity: Medium promotes signals at Medium, High, and Critical.
AdvisoryPipeline Behavior
The AdvisoryPipeline wraps multiple AdvisoryGuard implementations and a PromotionPolicy. It implements the kernel's Guard trait so it can register in the standard pipeline. Guard name: advisory-pipeline.
- The pipeline evaluates every registered advisory guard in order.
- Each guard returns zero or more
AdvisorySignalentries. - For each signal, the pipeline checks the promotion policy.
- If any signal matches a promotion rule, it is marked
promoted: trueand the pipeline returnsVerdict::Deny. - If no signals are promoted, the pipeline returns
Verdict::Allow. - All collected signals (promoted or not) are stored for evidence export.
Without any promotion rules, the advisory pipeline MUST always return Verdict::Allow.
PromotionPolicy
Operators configure promotion rules in chio.yaml to convert advisory signals into deterministic denials:
advisory:
promotion_rules:
- guard_name: anomaly-advisory
min_severity: high
- guard_name: data-transfer-advisory
min_severity: critical| PromotionRule field | Type | Description |
|---|---|---|
guard_name | string | Exact match on the advisory guard's name |
min_severity | AdvisorySeverity | Minimum severity to promote |
When a signal matches a promotion rule (guard name matches and signal severity is at least the rule severity), the signal's promoted field is set to true and the pipeline returns Deny. Both PromotionRule and PromotionPolicy serialize via serde with snake_case severity values.
Built-in Advisory Guards
AnomalyAdvisoryGuard (anomaly-advisory): flags unusual invocation patterns and excessive delegation depth.
| Condition | Severity | Description |
|---|---|---|
| Tool invoked >= threshold times | Medium | Tool X invoked N times (threshold: T) |
| Tool invoked >= 2x threshold | High | Elevated severity for sustained repetition |
| Delegation depth >= threshold | High | Delegation depth D exceeds threshold T |
DataTransferAdvisoryGuard (data-transfer-advisory): flags sessions with high cumulative data transfer, useful as an early warning before the deterministic DataFlowGuard limit is hit.
| Condition | Severity | Description |
|---|---|---|
| Total bytes >= threshold | Medium | Cumulative transfer exceeds threshold |
| Total bytes >= 2x threshold | High | Elevated severity |
| Total bytes >= 3x threshold | Critical | Critical data volume |
Signals include total_bytes, bytes_read, bytes_written, and threshold in metadata. See guard-platform: advisory.
WASM Custom Guards
The chio-wasm-guards crate allows operators to author guards in any language that compiles to WebAssembly (Rust, AssemblyScript, Go, C) and load them into the Chio kernel at runtime.
Host-Guest ABI
Each .wasm guard module MUST export a single function:
evaluate(request_ptr: i32, request_len: i32) -> i32The host serializes a GuardRequest as JSON, writes the bytes into guest linear memory at offset 0, and calls evaluate(0, json_length). Return codes:
| Code | Meaning |
|---|---|
0 | Allow |
1 | Deny |
| Any negative value | Error (fail-closed) |
The guest MAY write a NUL-terminated UTF-8 deny reason starting at offset 65536 (64 KiB) in linear memory; the host reads up to 4096 bytes. If the region is absent, empty, or malformed, the host uses a generic denial message.
GuardRequest
| Field | Type | Description |
|---|---|---|
tool_name | string | Tool being invoked |
server_id | string | Server hosting the tool |
agent_id | string | Agent making the request |
arguments | object | Tool arguments (opaque JSON) |
scopes | string[] | Granted scope names (formatted as "server_id:tool_name") |
session_metadata | object or null | Optional session context for stateful guards |
Fuel Metering
WASM guards execute under a fuel budget that limits CPU consumption. The runtime tracks fuel consumption per instruction and terminates the guest when the budget is exhausted. Default fuel_limit: 10,000,000 units per invocation.
Fail-closed on every error
WasmGuardError::FuelExhausted). Any WASM trap (memory access violation, stack overflow, unreachable instruction) MUST result in denial. If the module does not export the required evaluate function or memory, the load MUST fail and the guard MUST NOT be registered in the pipeline.Configuration
wasm_guards:
- name: custom-pii-guard
path: /etc/chio/guards/pii_guard.wasm
fuel_limit: 5000000
priority: 100
advisory: falseWhen advisory: true, the guard logs denials and errors but returns Verdict::Allow. This lets operators test new WASM guards in production without blocking traffic.
Security Properties
- Sandboxed execution: WASM guards execute in an isolated linear memory space with no access to the host filesystem, network, or kernel state.
- Deterministic termination: fuel metering guarantees that guards terminate within bounded time.
- No host callbacks: the current ABI does not provide any host functions to the guest. The guest can only read the provided request and return a verdict.
- Fail-closed on all errors: compilation failure, missing exports, fuel exhaustion, traps, and unexpected return values all result in denial (for non-advisory guards).
Session Journal Contract
The session journal (chio-http-session crate) is the shared state layer that session-aware guards and advisory guards read from. It is an append-only, hash-chained log of request records within a single session.
Invariants
- Append-only: entries MUST only be added, never modified or removed.
- Hash-chained: each entry MUST include a SHA-256 hash of the previous entry for tamper detection. The first entry uses the zero hash (64 hex zeros) as its
prev_hash. - Thread-safe: the journal MUST be safe for concurrent access from multiple guards. The implementation uses a
Mutexaround the inner state. - Per-session scope: each session creates one journal, shared via
Chio<SessionJournal>with all guards that need it.
Journal Entry
| Field | Type | Description |
|---|---|---|
sequence | u64 | Monotonically increasing sequence number (0-based) |
prev_hash | string | SHA-256 hex hash of the previous entry (zero hash for the first entry) |
entry_hash | string | SHA-256 hex hash of this entry's canonical fields |
timestamp_secs | u64 | Unix timestamp (seconds) when the entry was recorded |
tool_name | string | Tool that was invoked |
server_id | string | Server that hosted the tool |
agent_id | string | Agent that made the invocation |
bytes_read | u64 | Bytes read during this invocation |
bytes_written | u64 | Bytes written during this invocation |
delegation_depth | u32 | Delegation depth at the time of invocation |
allowed | boolean | Whether the invocation was allowed or denied |
Entry hash computation: the entry_hash is the SHA-256 digest of the fields concatenated in this order using little-endian byte encoding for integers and UTF-8 for strings:
sequence(8 bytes, LE)prev_hash(UTF-8 bytes)timestamp_secs(8 bytes, LE)tool_name(UTF-8 bytes)server_id(UTF-8 bytes)agent_id(UTF-8 bytes)bytes_read(8 bytes, LE)bytes_written(8 bytes, LE)delegation_depth(4 bytes, LE)allowed(1 byte:0x01for true,0x00for false)
Cumulative Accounting
The journal maintains running cumulative statistics that guards read via data_flow(): total_bytes_read, total_bytes_written, total_invocations, and max_delegation_depth. All additions use saturating arithmetic to prevent overflow.
Integrity Verification
The verify_integrity() method walks the hash chain and verifies:
- Each entry's
prev_hashmatches the preceding entry'sentry_hash(or the zero hash for the first entry). - Each entry's
entry_hashmatches the recomputed hash of its canonical fields.
If either check fails, the journal returns SessionJournalError::IntegrityViolation with the index, expected hash, and actual hash. Operators SHOULD invoke integrity verification at session boundaries or during audit. Guards MAY verify integrity before reading journal state, though this adds latency and is not required for normal operation.
Guard Access Patterns
| Guard | Journal method | Usage |
|---|---|---|
DataFlowGuard | data_flow() | Read cumulative byte counts |
BehavioralSequenceGuard | tool_sequence() | Read ordered tool history |
AnomalyAdvisoryGuard | tool_counts(), data_flow() | Read per-tool counts and delegation depth |
DataTransferAdvisoryGuard | data_flow() | Read cumulative byte counts |
Implementation Status
| Guard | Crate | Status |
|---|---|---|
| InternalNetworkGuard | chio-guards | Full |
| AgentVelocityGuard | chio-guards | Full |
| DataFlowGuard | chio-guards | Full |
| BehavioralSequenceGuard | chio-guards | Full |
| ResponseSanitizationGuard | chio-guards | Full |
| PostInvocationPipeline | chio-guards | Full |
| AdvisoryPipeline | chio-guards | Full |
| AnomalyAdvisoryGuard | chio-guards | Full |
| DataTransferAdvisoryGuard | chio-guards | Full |
| WasmGuard | chio-wasm-guards | Full |
| WasmGuardRuntime | chio-wasm-guards | Full |
| SessionJournal | chio-http-session | Full |
Conformance Fixtures
The spec does not enumerate dedicated conformance fixtures here. Guard behavior is verified through the runtime test suites in each guard crate and through the broader Chio conformance scenarios; see the protocol-level conformance evidence under tests/conformance/ in the chio repository for the cross-cutting fixture set. Operators validating a guard configuration SHOULD pair guard configuration changes with replay-based verification using the kernel's signed receipts as ground truth.
Related
- Guard platform: Guard trait.
- Guard platform: pipelines.
- Guard platform: fail-closed.
- Guard platform: advisory.
- Formal: theorem inventory.
- Security: threat model and TCB boundary.
- HTTP substrate: HTTP-side evaluation pipeline.