PlatformAuthoring
HushSpec Policy Format
HushSpec is the YAML schema that declares what a Chio agent may do, compiled into a guard pipeline plus capability grants.
Source of record
crates/guards/chio-policy/src/models/tests.rs, crates/guards/chio-policy/src/compiler/tests.rs, and crates/guards/chio-policy/src/evaluate/tests.rs, alongside the type definitions in crates/guards/chio-policy/src/models.rs (rule structs live in the models/rules.rs submodule). If this page conflicts with the source, follow the source.Document Version
Every HushSpec document begins with a version line:
hushspec: "0.1.0"The current version is "0.1.0" (constant HUSHSPEC_VERSION). This is the only supported value listed in HUSHSPEC_SUPPORTED_VERSIONS. is_hushspec_format detects a HushSpec document by the presence of a top-level hushspec key alone; it never inspects the version value, so a document declaring hushspec: "9.9.9" is still recognized as HushSpec. Version support is enforced later, during validate(): version::is_supported() checks the declared version against HUSHSPEC_SUPPORTED_VERSIONS and fails validation — not format detection — for anything else.
Top-Level Fields
The root object uses deny_unknown_fields: any key not listed below is a parse-time error.
| Field | Type | Required | Description |
|---|---|---|---|
hushspec | string | Yes | Schema version. Always "0.1.0". |
name | string | No | Human-readable identifier carried into receipts. |
description | string | No | Free-form prose explaining intent. |
extends | string | No | Reference to a base policy (built-in or filesystem path). |
merge_strategy | enum | No | How the document folds into its parent. Default deep_merge. |
rules | object | No | Per-guard configuration. The core of the policy. |
extensions | object | No | Domain-specific add-ons: posture, origins, detection, reputation, runtime assurance, chio. |
metadata | object | No | Governance fields: author, classification, lifecycle. |
extends
extends takes a single reference string. The resolver in chio-policy::resolve loads the base, recurses on its own extends chain, and merges the children back over each parent. The composite loader supports two reference forms:
| Reference | Loader | Example |
|---|---|---|
| Built-in ruleset | Embedded YAML | chio:default, chio:strict, chio:permissive, chio:ai-agent, chio:cicd, chio:remote-desktop |
| Filesystem path | Reads from disk relative to the source policy | ../base/team.yaml, /etc/chio/policies/base.yaml |
Built-in references accept either the bare name or a chio: / hushspec: prefix. The catalogue lives in BUILTIN_RULESETS and ships default, strict, permissive, ai-agent, cicd, remote-desktop, and the reserved panic ruleset.
HTTP references are not supported
http:// and https:// references withResolveError::Http. Bring remote bases onto the filesystem first (for example, as a generated file) and reference the local path.Circular Detection
The resolver tracks every loaded canonical source on a stack. If a descendant resolves back to a source already in the stack, it returns ResolveError::Cycle with the full chain (joined with -> ). Cycles cannot compile.
merge_strategy
The merge strategy controls how the child policy combines with the resolved parent. Values are rendered in snake_case:
| Strategy | Behavior |
|---|---|
deep_merge (default) | Merges the extensions sub-blocks field-by-field. Rule blocks are still whole-block-replaced, exactly as under merge: a rule block the child declares replaces the parent's block outright. |
merge | Top-level shallow merge: child blocks fully replace any same-named parent block but other top-level blocks are preserved. |
replace | Child wholly replaces the parent. The extends chain is still loaded for source provenance, but no fields carry forward. |
The default is deep_merge, but its difference from merge is narrow. Both fold rule blocks by whole-block replacement: when the child declares a rule block it replaces the parent's block entirely — a child forbidden_paths.patterns list replaces the parent's list, it is not appended to it — and when the child omits a block the parent's carries forward. The two strategies diverge only on extensions: deep_merge merges each extension (posture, origins, detection, reputation, chio) field-by-field, so a child can override one posture state without redeclaring the rest, while merge replaces the whole extension block.
rules
The rules object holds per-guard configuration. The struct uses deny_unknown_fields; unknown rule names are rejected. Every documented sub-block is optional. Omitted blocks compile to disabled guards (the default pipeline still applies a minimal safe baseline if extends: chio:default is in scope).
| Block | Compiles to |
|---|---|
forbidden_paths | ForbiddenPathGuard |
path_allowlist | PathAllowlistGuard |
egress | EgressAllowlistGuard |
secret_patterns | SecretLeakGuard |
patch_integrity | PatchIntegrityGuard |
shell_commands | ShellCommandGuard |
tool_access | McpToolGuard + capability grants |
computer_use | ComputerUseGuard |
remote_desktop_channels | RemoteDesktopSideChannelGuard |
input_injection | InputInjectionCapabilityGuard |
browser_automation | BrowserAutomationGuard |
code_execution | CodeExecutionGuard |
velocity | VelocityGuard (+ AgentVelocityGuard) |
human_in_loop | Constraint::RequireApprovalAbove on grants |
tool_access (Detail)
rules:
tool_access:
enabled: true # bool, default true
default: block # "allow" or "block", default "allow"
allow: [read_file] # tools explicitly permitted
block: [delete_file] # tools explicitly denied (overrides allow)
require_confirmation: # tools that require human approval
- write_file
max_args_size: 4096 # optional cap on serialized argument size
require_runtime_assurance_tier: hardware_isolated # optional
prefer_runtime_assurance_tier: hardware_isolated # optional
require_workload_identity: # optional
scheme: spiffe
trust_domain: example.org
path_prefixes: ["/agents/"]
credential_kinds: [x509]For details on every other rule (default values, list semantics, examples), the field-by-field tables in Write a Policy are the operator-facing reference. The Rust struct definitions below are the schema:
pub struct ForbiddenPathsRule {
pub enabled: bool, // default true
pub patterns: Vec<String>, // glob patterns to deny
pub exceptions: Vec<String>, // exempt specific paths
}
pub struct PathAllowlistRule {
pub enabled: bool, // default false
pub read: Vec<String>,
pub write: Vec<String>,
pub patch: Vec<String>,
}
pub struct EgressRule {
pub enabled: bool, // default true
pub allow: Vec<String>,
pub block: Vec<String>,
pub default: DefaultAction, // default Block
}
pub struct PatchIntegrityRule {
pub enabled: bool, // default true
pub max_additions: usize, // default 1000
pub max_deletions: usize, // default 500
pub forbidden_patterns: Vec<String>,
pub require_balance: bool, // default false
pub max_imbalance_ratio: f64, // default 10.0
}
pub struct VelocityRule {
pub enabled: bool, // default true
pub max_invocations_per_window: Option<u32>,
pub max_spend_per_window: Option<u64>, // minor units
pub max_requests_per_agent: Option<u32>,
pub max_requests_per_session: Option<u32>,
pub window_secs: u64, // default 60
pub burst_factor: f64, // default 1.0
}Conditional Activation
Rule blocks can be gated on runtime context. Conditions are supplied as a map keyed by rule-block name; each value is a Condition from chio_policy::conditions. validate_condition_keys rejects any key that is not a known rule-block name, so a misspelled block fails closed rather than silently leaving the rule active.
Condition is a small, non-Turing-complete predicate type with these fields:
time_window— an optionalTimeWindowCondition(start,end, optionaltimezoneand days-of-week) matched against the request time.context— an optional key/value map matched against theRuntimeContextmaps (user,agent,session,request,deployment,custom). There are no dedicated tool-name or agent-id fields; those are just keys inside these generic maps.all_of,any_of,not— boolean composition over nested conditions (AND / OR / NOT), nested up to a depth of eight.
evaluate_condition(condition, context) is fail-closed: every field present on a condition must hold, a missing context field evaluates to false, and nesting past depth eight also returns false. A bare Condition with several fields set therefore ANDs them together; any_of is the only OR.
extensions
Extensions hold optional, domain-specific configuration. The shape is fixed: unknown sub-blocks are rejected by deny_unknown_fields.
| Extension | Purpose |
|---|---|
posture | Stateful posture machine: initial state, state-specific capabilities/budgets, transitions on triggers. |
origins | Per-origin profiles with their own tool_access and egress overrides; default behavior is deny or minimal_profile. |
detection | Prompt-injection, jailbreak, and threat-intel detectors (regex and similarity-based). |
reputation | Reputation tiers, scoring weights, promotion/demotion triggers and metrics requirements. |
runtime_assurance | Tier requirements and verifier rules sourced from chio_core::appraisal. |
chio | Chio-specific overlay: market hours, signing, k8s namespaces, rollback, n-of-m approver sets. |
Each extension has its own merge function in chio-policy::merge so deep-merge respects the structure (e.g., posture states keyed by name, origin profiles keyed by id).
metadata
metadata carries governance fields. None are enforced by the runtime; they exist for audit pipelines and deployment review.
| Field | Type | Notes |
|---|---|---|
author | string | Free-form. |
approved_by | string | Free-form. |
approval_date | string | RFC 3339 recommended. |
classification | enum | public | internal | confidential | restricted |
change_ticket | string | Reference to your change-management system. |
lifecycle_state | enum | draft | review | approved | deployed | deprecated | archived |
policy_version | int | Internal version counter (separate from hushspec). |
effective_date | string | When the policy starts applying. |
expiry_date | string | When the policy expires. |
Compile Model
Compilation runs in three stages:
- Parse.
HushSpec::parsereads the YAML, applies pre-checks against malformed scalars, and deserializes withdeny_unknown_fields. - Resolve and merge.
resolve_with_loaderwalks theextendschain, detecting cycles, then folds children over parents permerge_strategy. - Compile.
compile_policyturns the resolvedHushSpecinto aCompiledPolicy: a guard pipeline plus initial capability grants. A separate control-plane path,build_guard_pipeline/build_post_invocation_pipeline(re-exported fromchio_control_plane::policy), extends this with cloud-guardrail and threat-intel adapters from a separate external-guard policy, not from the HushSpec document.
The compiled output drives the kernel's evaluation pipeline. See Kernel Architecture for the runtime flow.
Two evaluation paths
compile_policy() is not the only way a HushSpec document gets evaluated. A separate evaluate() interpreter reads the same document and returns a tri-state Decision::{Allow, Warn, Deny} without building or running the guard pipeline. This interpreter backs dry-run, explainability, and simulation. See Testing Guards & Policies for the full workflow.Validation Rules
The following are enforced by the parser, resolver, or compiler. They are exercised in the test suites at crates/guards/chio-policy/src/models/tests.rs, crates/guards/chio-policy/src/compiler/tests.rs, and crates/guards/chio-policy/src/evaluate/tests.rs.
- Unknown fields are rejected.
deny_unknown_fieldsapplies toHushSpec,Rules, every rule struct, and every extension. Typos likeforbidden_path(singular) instead offorbidden_pathsare parse-time errors. - YAML must be a mapping. A document that begins with a plain scalar, a sequence, or a URL fails before libyml runs. This is a defense against accidentally-pasted text.
- Quoted scalar overflow is rejected. Long whitespace runs inside quoted scalars (over
MAX_QUOTED_SCALAR_WHITESPACE_RUN) fail before reaching libyml so the underlying parser is not forced through an exponential path. - Cycles in
extendsare rejected. The resolver returnsResolveError::Cyclewith the chain. - HTTP references are rejected. The composite loader returns
ResolveError::Httpforhttp://andhttps://references. - Regex patterns are validated.
shell_commands.forbidden_patterns,patch_integrity.forbidden_patterns, andsecret_patterns.patterns[].patterngo throughchio_policy::regex_safetyand reject pathological patterns. - Default actions are enums.
tool_access.defaultandegress.defaultaccept onlyalloworblock. Other strings fail to deserialize. - Posture references are validated.
validate_posturechecks thatposture.initialand everyposture.transitions[].from/toname a state defined inposture.states, and that no transition targets"*". There is no duplicate-guard-name rejection — the compiler records guard names in a parallel list with no uniqueness check — and no cross-reference validation tyingextensions.originsto posture states. Required-field checks on external-guard providers (a non-emptyapi_keyorendpoint) live on the separate control-plane external-guard compile path, not in HushSpec validation.
Worked Example
A team policy that extends the built-in chio:default ruleset, tightens forbidden paths, locks down tools to a project allowlist, adds a velocity cap, and records governance metadata.
hushspec: "0.1.0"
name: team-data-pipeline
description: Read-only data extraction agent with strict path and tool gates.
extends: "chio:default"
merge_strategy: deep_merge
rules:
tool_access:
enabled: true
default: block
allow:
- read_file
- list_directory
- search_files
- run_query
block:
- execute_command
require_confirmation:
- export_to_csv
max_args_size: 8192
forbidden_paths:
enabled: true
patterns:
- "**/.env*"
- "**/secrets/**"
- "**/.aws/credentials"
- "**/id_rsa*"
exceptions:
- "/var/lib/agent/.env.testing"
path_allowlist:
enabled: true
read:
- "/var/lib/agent/data/**"
- "/var/lib/agent/config/*.yaml"
write: []
patch: []
egress:
enabled: true
default: block
allow:
- "warehouse.internal"
- "*.metrics.example.com"
secret_patterns:
enabled: true
patterns:
- name: aws_access_key
pattern: "AKIA[0-9A-Z]{16}"
severity: critical
- name: bearer_token
pattern: "Bearer\\s+[A-Za-z0-9._~+/-]+=*"
severity: error
velocity:
enabled: true
max_invocations_per_window: 200
window_secs: 60
burst_factor: 1.5
human_in_loop:
enabled: true
require_confirmation:
- "export_*"
timeout_seconds: 300
on_timeout: deny
extensions:
chio:
market_hours:
tz: "America/New_York"
open: "09:30"
close: "16:00"
days: [Mon, Tue, Wed, Thu, Fri]
metadata:
author: "platform-security"
approved_by: "secops-lead"
classification: confidential
lifecycle_state: deployed
policy_version: 7
effective_date: "2026-04-01"Next Steps
- Write a Policy · field-by-field tables for every rule with worked examples
- Inherit & Merge Policies · base policies, overlay patterns, and multi-environment inheritance
- chio.yaml Configuration · the runtime config that loads a compiled policy and registers guards
- External Guards · the separate control-plane external-guard configuration (
cloud_guardrailsandthreat_intel), separate from HushSpec rules