ReferenceSpec
HTTP Substrate
A local Chio sidecar evaluates HTTP requests against policy, signs a receipt, and returns a structured verdict.
When to use this page
ChioHttpRequest, HttpReceipt, verdict enum, default policy, and the deterministic HttpReceipt → ChioReceipt mapping. Don't use this if: you want the agent-protocol wire format (see Wire Protocol) or the shared capability and receipt contract (see Protocol Reference).Source
This page normatively reflects spec/HTTP-SUBSTRATE.md in the chio repository. Status: Normative. Version 1.0. The keywords MUST, SHOULD, and MAY are normative per RFC 2119.
Overview
The HTTP sidecar protocol uses a local evaluation model: a Chio kernel runs as a local process and exposes an HTTP API on localhost. Language-specific middleware (Express, Actix, Axum, etc.) intercepts incoming HTTP requests, constructs a ChioHttpRequest, sends it to the sidecar, and enforces the returned verdict.
The sidecar protocol defines:
- A sidecar evaluation protocol (three HTTP endpoints)
- A typed request model (
ChioHttpRequest) for policy evaluation - A typed receipt model (
HttpReceipt) for signed proof of evaluation - Supporting types for caller identity, authentication, sessions, and verdicts
- A deterministic mapping from
HttpReceiptto the coreChioReceipttype
Sidecar Evaluation Protocol
Transport
The sidecar MUST listen on 127.0.0.1:9090 by default. Implementations MAY override the sidecar URL via:
- An explicit configuration value (
sidecarUrlin SDK config, or equivalent) - The
CHIO_SIDECAR_URLenvironment variable
If both are set, the explicit configuration value takes precedence. If neither is set, the default http://127.0.0.1:9090 MUST be used. All request and response bodies MUST use Content-Type: application/json.
Endpoints
| Endpoint | Method | Purpose |
|---|---|---|
/chio/evaluate | POST | Evaluate an HTTP request against policy |
/chio/verify | POST | Verify a receipt signature |
/chio/health | GET | Sidecar health check |
POST /chio/evaluate
Evaluates an HTTP request against the loaded policy and returns a signed receipt.
Request body: ChioHttpRequest.
Response body: EvaluateResponse.
| Field | Type | Required | Description |
|---|---|---|---|
verdict | Verdict | MUST | The evaluation verdict |
receipt | HttpReceipt | MUST | Signed receipt that records the evaluation |
evidence | GuardEvidence[] | MUST | Guard evidence collected during evaluation |
execution_nonce | SignedExecutionNonce or null | MAY | Present only when the kernel is configured for strict/opt-in nonce mode and issues one (allow verdict). The client MUST re-present it as ToolCallRequest.execution_nonce before executing the tool call. Absent on deny/cancel and on deployments without a nonce config. |
Status codes:
200 OK. Evaluation completed (regardless of verdict). Theverdictfield indicates whether the request was allowed or denied.400 Bad Request. The request body is malformed or missing required fields.500 Internal Server Error. The sidecar encountered an internal error during evaluation.
200 means evaluated, not allowed
verdict field.POST /chio/verify
Verifies the signature, content-addressed id, and authority semantics of a previously issued HttpReceipt.
Request body: HttpReceipt.
Response body: VerifyReceiptResponse. There is no single valid field; the response decomposes verification into these fields:
| Field | Type | Description |
|---|---|---|
signature_valid | boolean | Whether the receipt signature verifies against kernel_key |
signer_trusted | boolean | Whether the signing key is in the verifier's trusted set |
receipt_id_valid | boolean | Whether id matches the recomputed content-addressed id |
parameter_hash_valid | boolean | Whether content_hash is a well-formed 64-char lowercase hex digest |
receipt_kind | string | The receipt's semantic class |
boundary_class | string | The receipt's runtime boundary class |
trust_level | string | The receipt's trust level |
result | string | Verdict result: allow, deny, cancelled, or incomplete |
authorized | boolean | ok and the verdict is allow. This is the field to gate execution on. |
signer_key_hex | string | Hex encoding of the signing key |
ok | boolean | Roll-up: signature valid, signer trusted, id valid, parameter hash valid, and authority semantics well-formed |
A receipt with a valid signature but an expired timestamp still reports signature_valid: true. Temporal validity is the caller's responsibility.
GET /chio/health
| Field | Type | Required | Description |
|---|---|---|---|
status | string | MUST | One of "healthy", "degraded", or "unhealthy" |
version | string | MUST | Sidecar version string |
receipt_backend | string | MUST | Backend of the embedded kernel's receipt log: durable or ephemeral |
revocation_backend | string | MUST | Backend of the embedded kernel's revocation state: durable, remote, or ephemeral |
Status codes: 200 OK if healthy or degraded; 503 Service Unavailable if unhealthy.
Timeout and Failure Behavior
SDKs MUST implement a configurable timeout for sidecar calls. The default timeout SHOULD be 5000 milliseconds.
Fail-closed by default
ChioHttpRequest
The protocol-agnostic HTTP request that Chio evaluates. This is the shared input type for all HTTP sidecar adapters (reverse proxy, framework middleware, and sidecar alike).
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
request_id | string | MUST | Unique request identifier. UUIDv7 recommended. | |
method | HttpMethod | MUST | HTTP method of the request | |
route_pattern | string | MUST | Matched route pattern (e.g., "/pets/{petId}") | |
path | string | MUST | Actual request path (e.g., "/pets/42") | |
query | map<string, string> | MAY | {} | Query parameters |
headers | map<string, string> | MAY | {} | Selected headers. Adapters MUST NOT include raw auth credential headers. |
caller | CallerIdentity | MUST | Extracted caller identity | |
body_hash | string or null | MAY | null | SHA-256 hex hash of the request body. null for bodyless requests (GET, HEAD, OPTIONS). |
body_length | integer | MAY | 0 | Content-Length of the request body in bytes |
session_id | string or null | MAY | null | Session ID this request belongs to |
capability_id | string or null | MAY | null | Capability token ID presented with this request |
timestamp | integer | MUST | Unix timestamp (seconds) when the request was received |
Content Hash Computation
The content hash that binds a request to its receipt is computed as the SHA-256 hex digest of the canonical JSON (RFC 8785) of the following binding object:
{
"body_hash": "<body_hash or null>",
"method": "<HTTP method>",
"path": "<actual path>",
"query": { "<sorted query params>" },
"route_pattern": "<route pattern>"
}CallerIdentity
The identity of the caller as extracted from the HTTP request.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
subject | string | MUST | Stable identifier for the caller | |
auth_method | AuthMethod | MUST | How the caller authenticated | |
verified | boolean | MUST | false | Whether this identity has been cryptographically verified |
tenant | string or null | MAY | null | Tenant or organization the caller belongs to |
agent_id | string or null | MAY | null | Agent identifier when the caller is an AI agent |
Identity hash: the caller_identity_hash in receipts is the SHA-256 hex digest of the canonical JSON representation of the full CallerIdentity object. The same identity MUST always produce the same hash.
AuthMethod
A tagged union representing how the caller authenticated. The discriminator field is method. Variant names use snake_case.
| Variant | Tag value | Fields | Description |
|---|---|---|---|
| Bearer | "bearer" | token_hash: string | Bearer token (JWT or opaque). Implementations MUST NOT store or transmit raw tokens. |
| ApiKey | "api_key" | key_name, key_hash | API key. SHA-256 hex hash of the key value. |
| Cookie | "cookie" | cookie_name, cookie_hash | Session cookie. SHA-256 hex hash of the cookie value. |
| MtlsCertificate | "mtls_certificate" | subject_dn, fingerprint | mTLS client certificate. SHA-256 fingerprint. |
| Anonymous | "anonymous" | (none) | No authentication was presented. |
{ "method": "bearer", "token_hash": "a1b2c3d4e5f6..." }SessionContext
Per-session context carried through the Chio HTTP pipeline. A session groups related requests from the same caller over a bounded time window.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
session_id | string | MUST | Unique session identifier | |
caller | CallerIdentity | MUST | Authenticated caller for this session | |
created_at | integer | MUST | Unix timestamp (seconds) when the session was created | |
expires_at | integer or null | MAY | null | Unix timestamp (seconds) when the session expires |
request_count | integer | MAY | 0 | Number of requests evaluated in this session |
bytes_read | integer | MAY | 0 | Cumulative bytes read by this session |
bytes_written | integer | MAY | 0 | Cumulative bytes written by this session |
delegation_depth | integer | MAY | 0 | Current delegation depth. 0 means direct caller. |
metadata | object or null | MAY | null | Extensibility metadata |
Verdict
Internally tagged with "verdict" as the tag key and snake_case variant names.
| Variant | Tag value | Fields | Description |
|---|---|---|---|
| Allow | "allow" | (none) | Request is allowed. Proceed to upstream. |
| Deny | "deny" | reason, guard, http_status, details | Request is denied. Default http_status is 403. details is a boxed DenyDetails block, omitted from the wire when empty. |
| Cancel | "cancel" | reason | Evaluation was cancelled (e.g., timeout, circuit breaker). |
| Incomplete | "incomplete" | reason | Evaluation did not reach a terminal state. |
Fail-closed semantics
cancel or incomplete, middleware MUST treat the request as denied. Only an explicit allow verdict permits forwarding to the upstream API.When deserializing a deny verdict without an explicit http_status field, implementations MUST default to 403.
DenyDetails
A deny verdict carries an optional boxed DenyDetails block so a sidecar can tell an SDK exactly what scope was required versus granted. Every field is optional; the whole details key is omitted from the wire when all fields are empty.
| Field | Type | Description |
|---|---|---|
tool_name | string or null | Tool that was denied |
tool_server | string or null | Tool server that hosts the denied tool |
requested_action | string or null | Short summary of the attempted action |
required_scope | string or null | Scope the kernel says is required, as a canonical ToolGrant(...) string |
granted_scope | string or null | Scope the presented capability actually had. Null when none was presented. |
reason_code | string or null | Stable machine-readable code, e.g. scope.missing |
receipt_id | string or null | Receipt id capturing this denial, for audit correlation |
hint | string or null | Next-steps sentence for the developer |
docs_url | string or null | Link to the docs page explaining this deny code |
HttpMethod
Serialized as uppercase strings.
| Value | Safe | Requires Capability |
|---|---|---|
"GET" | Yes | No |
"HEAD" | Yes | No |
"OPTIONS" | Yes | No |
"POST" | No | Yes |
"PUT" | No | Yes |
"PATCH" | No | Yes |
"DELETE" | No | Yes |
Safe methods (GET, HEAD, OPTIONS) are considered side-effect-free by default and receive session-scoped allow verdicts. Unsafe methods (POST, PUT, PATCH, DELETE) require an explicit capability grant.
GuardEvidence
| Field | Type | Required | Description |
|---|---|---|---|
guard_name | string | MUST | Name of the guard |
verdict | boolean | MUST | Whether the guard passed (true) or denied (false) |
details | string or null | MAY | Human-readable details about the guard's decision |
HttpReceipt
A signed receipt proving that an HTTP request was evaluated by the Chio kernel. The receipt binds the request identity, route, method, verdict, semantic classification, and guard evidence under a signature from the kernel (Ed25519 by default; P-256 and P-384 also supported).
| Field | Type | Required | Default | Signed | Description |
|---|---|---|---|---|---|
id | string | MUST | Yes | Content-addressed receipt id: SHA-256 hex of the canonical JSON of the receipt body with id removed (compute_http_receipt_id), computed and assigned inside HttpReceipt::sign. Not a UUIDv7 and not caller-assignable. | |
request_id | string | MUST | Yes | Unique request ID this receipt covers | |
route_pattern | string | MUST | Yes | Matched route pattern | |
method | HttpMethod | MUST | Yes | HTTP method of the evaluated request | |
caller_identity_hash | string | MUST | Yes | SHA-256 hex hash of the caller identity | |
session_id | string or null | MAY | null | Yes | Session ID the request belonged to |
verdict | Verdict | MUST | Yes | The kernel's verdict | |
receipt_kind | ReceiptKind | MUST | Yes | Signed semantic class. Always mediated_decision for HTTP receipts. | |
boundary_class | BoundaryClass | MUST | Yes | Signed runtime boundary. Always prevent for HTTP receipts. | |
observation_outcome | ObservationOutcome or null | MUST NOT | null | Yes | Required absent on mediated HTTP receipts; signing rejects a present value. |
tool_origin | ToolOrigin | MUST | Yes | Where the protected effect executes relative to Chio (caller_executed, host_executed_provider_reported, host_executed_unmediated) | |
redaction_mode | RedactionMode | MUST | Yes | Redaction applied to signed details (none, summary, redacted) | |
actor_chain | ActorRef[] | MAY | [] | Yes | Signed actor attribution for the HTTP decision. Omitted from the wire when empty. |
evidence | GuardEvidence[] | MAY | [] | Yes | Per-guard evidence collected during evaluation |
response_status | integer | MUST | Yes | HTTP status Chio associated with the evaluation outcome at receipt-signing time. | |
timestamp | integer | MUST | Yes | Unix timestamp (seconds) when the receipt was created | |
content_hash | string | MUST | Yes | SHA-256 hex hash binding the request content to this receipt | |
policy_hash | string | MUST | Yes | SHA-256 hex hash of the policy that was applied | |
trust_level | TrustLevel | MUST | Yes | Strength of kernel mediation. Always mediated for HTTP receipts. | |
capability_id | string or null | MAY | null | Yes | Capability ID that was exercised, if any |
metadata | object or null | MAY | null | Yes | Extensibility metadata |
kernel_key | PublicKey (hex) | MUST | Yes | Kernel's algorithm-aware public key (the same chio_core_types::crypto::PublicKey as the core receipt): bare 64-hex Ed25519, p256:<130-hex>, or p384:<194-hex> | |
signature | Signature (hex) | MUST | No | Algorithm-aware signature over canonical JSON of the body fields: bare 128-hex Ed25519, or a p256:/p384:-prefixed form |
Signing
To produce a signed receipt:
- Construct an
HttpReceiptBodycontaining all fields from the table above exceptsignature. - Compute the content-addressed
id(compute_http_receipt_id) and validate the authority semantics (mediated_decision+prevent+mediated, noobservation_outcome). - Serialize the body to canonical JSON (RFC 8785).
- Sign the canonical bytes with the kernel's private key (Ed25519 by default; P-256 or P-384 when configured).
- Attach the resulting signature to the receipt.
Verification
- Extract the body (all fields except
signature). - Serialize the body to canonical JSON (RFC 8785).
- Verify the signature against the canonical bytes using the
kernel_keyembedded in the receipt.
Implementations SHOULD verify the receipt signature before treating receipt content as authoritative.
Cryptographic Representations
kernel_key and signature reuse the same algorithm-aware chio_core_types::crypto types as the core ChioReceipt (imported directly), so they are not restricted to bare Ed25519 hex:
kernel_key: bare 64-character lowercase hex for Ed25519, or ap256:<130-hex>/p384:<194-hex>prefixed form.signature: bare 128-character lowercase hex for Ed25519, or ap256:/p384:prefixed DER-hex form. Verification dispatches off the self-describing encoding.
HttpReceipt to ChioReceipt Mapping
An HttpReceipt maps into the core ChioReceipt type for unified storage and querying. There are two methods, and the no-argument one fails closed by design:
to_chio_receipt()(no argument) always returns an error ("cannot convert HttpReceipt into signed ChioReceipt without the kernel keypair"), because a copied HTTP signature would not verify over theChioReceiptBody.to_chio_receipt_with_keypair(keypair)builds theChioReceiptBody, recomputescontent_hashover that canonical body, and re-signs viaChioReceipt::sign(...)with the supplied kernel keypair. The old HTTP signature is never copied.
Field Mapping
| HttpReceipt field | ChioReceipt field | Transformation |
|---|---|---|
id | id | Copied directly |
timestamp | timestamp | Copied directly |
capability_id | capability_id | Unwrapped; defaults to empty string if null |
| (constant) | tool_server | Set to "http" |
method + route_pattern | tool_name | Formatted as "{method} {route_pattern}" |
method, route_pattern, request_id | action.parameters | JSON object { method, route, request_id } |
| (derived) | action.parameter_hash | Canonical hash of action.parameters (via ToolCallAction::from_parameters), so a durable store accepts the converted receipt — not the HTTP content_hash |
verdict | decision | Converted via Verdict.to_decision() |
receipt_kind, boundary_class, observation_outcome, tool_origin, redaction_mode, actor_chain | (same fields) | Copied directly |
content_hash | content_hash | Recomputed as SHA-256 of the canonical JSON of the ChioReceiptBody |
policy_hash | policy_hash | Copied directly |
evidence | evidence | Copied directly |
metadata | metadata | Copied directly |
trust_level | trust_level | Copied directly |
| (constant) | tenant_id | Set to null |
kernel_key | kernel_key | Copied directly |
| (re-signed) | signature | Freshly produced by ChioReceipt::sign(...) with the supplied keypair. The HTTP signature is never copied. |
Verdict to Decision Mapping
| Verdict variant | Decision variant |
|---|---|
allow | allow |
deny { reason, guard, http_status } | deny { reason, guard } (http_status is dropped) |
cancel { reason } | cancelled { reason } |
incomplete { reason } | incomplete { reason } |
Conversion Re-Signs, or Fails Closed
No-argument conversion fails closed
to_chio_receipt() takes no keypair and unconditionally returns an error. A ChioReceipt signature cannot be derived from an HttpReceipt alone, so the conversion refuses rather than emit a receipt whose signature would fail standard verification.Use to_chio_receipt_with_keypair(keypair) for conversion. It recomputes content_hash over the canonical ChioReceiptBody and re-signs with the kernel keypair, producing a ChioReceipt whose signature and action.parameter_hash verify, so a durable receipt store accepts it.
Default Policy Semantics
The HTTP sidecar protocol defines default policy behavior based on HTTP method safety:
- Safe methods (GET, HEAD, OPTIONS): Session-scoped allow.
- Unsafe methods (POST, PUT, PATCH, DELETE): Deny by default. These methods require an explicit capability token presented in the
X-Chio-Capabilityrequest header or viacapability_idin theChioHttpRequest.
When a route is not matched in the loaded policy, the evaluator MUST fall back to method-based default policy.
Error Handling
Sidecar Error Codes
SDKs MUST use the following error codes when communicating sidecar failures to callers:
| Code | Meaning |
|---|---|
chio_access_denied | The request was denied by policy |
chio_sidecar_unreachable | The sidecar process is not reachable |
chio_evaluation_failed | The sidecar returned a non-200 status |
chio_invalid_receipt | Receipt verification failed |
chio_timeout | The sidecar did not respond within the timeout |
Structured Error Response
When middleware denies a request, the response body SHOULD be a structured JSON object:
| Field | Type | Required | Description |
|---|---|---|---|
error | string | MUST | Error code from the table above |
message | string | MUST | Human-readable error message |
receipt_id | string or null | MAY | Receipt ID for the denied evaluation |
suggestion | string or null | MAY | Actionable suggestion for the caller |
Schemas and Conformance
Versioned HTTP sidecar schemas live under spec/schemas/chio-http/v1/. Schema files in that directory are the machine-readable contract for the HTTP sidecar types. Implementations MUST serialize all HTTP sidecar types in a form accepted by those schemas. Schema validation SHOULD be exercised against live Rust serialization, not handwritten examples alone.
| File | Described type |
|---|---|
http-receipt.schema.json | HttpReceipt |
chio-http-request.schema.json | ChioHttpRequest |
caller-identity.schema.json | CallerIdentity with AuthMethod |
verdict.schema.json | Verdict |
evaluate-request.schema.json | POST /chio/evaluate request body |
evaluate-response.schema.json | POST /chio/evaluate response body |