PlatformAuthoring & Portability
Kernel
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"HUSHSPEC_VERSION is "0.1.0", and it is the only value HUSHSPEC_SUPPORTED_VERSIONS lists. Detection and support are separate steps. is_hushspec_format decides that a document is a HushSpec from the presence of the top-level hushspec key alone and never reads its value, so hushspec: "9.9.9" is still recognized as HushSpec. The version is checked later, in validate(), where version::is_supported() compares it against the supported list and fails validation rather than format detection.
Top-level fields
The root object uses deny_unknown_fields: any key outside the list below is a parse-time error. Exactly one field is required.
| Field | Type | Required | What it carries |
|---|---|---|---|
hushspec | String | Yes | Schema version. Checked in validate(), not at format detection. |
name | Option<String> | No | Human-readable identifier carried into receipts. |
description | Option<String> | No | Free-form prose explaining intent. |
extends | Option<String> | No | Reference to a base policy: a built-in ruleset name or a filesystem path. |
merge_strategy | Option<MergeStrategy> | No | How this document folds into its parent. Defaults to deep_merge. |
rules | Option<Rules> | No | Per-guard configuration. The core of the policy. |
extensions | Option<Extensions> | No | Domain-specific add-ons: posture, origins, detection, reputation, runtime assurance, and the chio overlay. |
metadata | Option<GovernanceMetadata> | No | Governance fields for audit pipelines and deployment review. The runtime enforces none of them. |
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:ai-agent, chio:cicd, chio:default, chio:panic, chio:permissive, chio:remote-desktop, chio:strict |
| 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: or hushspec: prefix. The catalogue is BUILTIN_RULESETS, and it ships one embedded YAML document per name above, including 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, so a child forbidden_paths.patterns list replaces the parent's list rather than appending to it. 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 in 14 named blocks. It uses deny_unknown_fields, so an unknown block name is a parse-time error, and RULE_BLOCK_NAMES is the same inventory in constant form for validators and evaluators. Every block is optional, and every one of them is gated twice: an omitted block adds no guard, and a block whose enabled is false adds no guard either. The compiler reads enabled before it constructs anything.
| Block | Struct | Compiles to |
|---|---|---|
forbidden_paths | ForbiddenPathsRule | ForbiddenPathGuard |
path_allowlist | PathAllowlistRule | PathAllowlistGuard |
egress | EgressRule | EgressAllowlistGuard, plus InternalNetworkGuard as its SSRF companion |
secret_patterns | SecretPatternsRule | SecretLeakGuard on the write path, plus a post-invocation SanitizerHook on the read path |
patch_integrity | PatchIntegrityRule | PatchIntegrityGuard |
shell_commands | ShellCommandsRule | ShellCommandGuard |
tool_access | ToolAccessRule | McpToolGuard, plus the capability grants and their MaxArgsSize and MinimumRuntimeAssurance constraints |
computer_use | ComputerUseRule | ComputerUseGuard |
remote_desktop_channels | RemoteDesktopChannelsRule | RemoteDesktopSideChannelGuard |
input_injection | InputInjectionRule | InputInjectionCapabilityGuard |
browser_automation | BrowserAutomationRule | BrowserAutomationGuard |
code_execution | CodeExecutionRule | CodeExecutionGuard |
velocity | VelocityRule | VelocityGuard and AgentVelocityGuard, both threaded with the configured process memory budget |
human_in_loop | HumanInLoopRule | Constraint::RequireApprovalAbove { threshold_units } on the compiled grants |
Two blocks are not one-to-one. egress also installs an InternalNetworkGuard so that the allowlist catches unknown domains while the companion catches raw RFC 1918 and cloud-metadata addresses, and secret_patterns installs a pre-invocation guard for the write path and a post-invocation SanitizerHook for the read path from the same configuration. Policy Compilation owns the full compile surface, including the guards that come from extensions rather than from rules.
Every block, field by field
Each block struct also carries deny_unknown_fields. Field names and types below are the struct definitions; the default column gives what a missing key deserializes to.
| Block | Field | Type | Default |
|---|---|---|---|
forbidden_paths | enabled | bool | true |
forbidden_paths | patterns | Vec<String> | empty |
forbidden_paths | exceptions | Vec<String> | empty |
path_allowlist | enabled | bool | false |
path_allowlist | read | Vec<String> | empty |
path_allowlist | write | Vec<String> | empty |
path_allowlist | patch | Vec<String> | empty |
egress | enabled | bool | true |
egress | allow | Vec<String> | empty |
egress | block | Vec<String> | empty |
egress | default | DefaultAction | block |
secret_patterns | enabled | bool | true |
secret_patterns | patterns | Vec<SecretPattern> | empty |
secret_patterns | skip_paths | Vec<String> | empty |
patch_integrity | enabled | bool | true |
patch_integrity | max_additions | usize | 1000 |
patch_integrity | max_deletions | usize | 500 |
patch_integrity | forbidden_patterns | Vec<String> | empty |
patch_integrity | require_balance | bool | false |
patch_integrity | max_imbalance_ratio | f64 | 10.0 |
shell_commands | enabled | bool | true |
shell_commands | forbidden_patterns | Vec<String> | empty |
tool_access | enabled | bool | true |
tool_access | allow | Vec<String> | empty |
tool_access | block | Vec<String> | empty |
tool_access | require_confirmation | Vec<String> | empty |
tool_access | default | DefaultAction | allow |
tool_access | max_args_size | Option<usize> | None |
tool_access | require_runtime_assurance_tier | Option<RuntimeAssuranceTier> | None |
tool_access | prefer_runtime_assurance_tier | Option<RuntimeAssuranceTier> | None |
tool_access | require_workload_identity | Option<WorkloadIdentityMatch> | None |
tool_access | prefer_workload_identity | Option<WorkloadIdentityMatch> | None |
computer_use | enabled | bool | false |
computer_use | mode | ComputerUseMode | guardrail |
computer_use | allowed_actions | Vec<String> | empty |
remote_desktop_channels | enabled | bool | false |
remote_desktop_channels | clipboard | bool | false |
remote_desktop_channels | file_transfer | bool | false |
remote_desktop_channels | audio | bool | true |
remote_desktop_channels | drive_mapping | bool | false |
input_injection | enabled | bool | false |
input_injection | allowed_types | Vec<String> | empty |
input_injection | require_postcondition_probe | bool | false |
browser_automation | enabled | bool | false |
browser_automation | allowed_domains | Vec<String> | empty |
browser_automation | blocked_domains | Vec<String> | empty |
browser_automation | allowed_verbs | Vec<String> | empty |
browser_automation | credential_detection | bool | true |
browser_automation | extra_credential_patterns | Vec<String> | empty |
code_execution | enabled | bool | false |
code_execution | language_allowlist | Vec<String> | empty |
code_execution | module_denylist | Vec<String> | empty |
code_execution | network_access | bool | false |
code_execution | max_execution_time_ms | Option<u64> | None |
code_execution | max_scan_bytes | Option<usize> | None |
velocity | enabled | bool | true |
velocity | max_invocations_per_window | Option<u32> | None |
velocity | max_spend_per_window | Option<u64> | None |
velocity | max_requests_per_agent | Option<u32> | None |
velocity | max_requests_per_session | Option<u32> | None |
velocity | window_secs | u64 | 60 |
velocity | burst_factor | f64 | 1.0 |
human_in_loop | enabled | bool | true |
human_in_loop | require_confirmation | Vec<String> | empty |
human_in_loop | approve_above | Option<u64> | None |
human_in_loop | approve_above_currency | Option<String> | None |
human_in_loop | timeout_seconds | Option<u64> | None |
human_in_loop | on_timeout | HumanInLoopTimeoutAction | deny |
Read the enabled defaults before anything else. path_allowlist, computer_use, remote_desktop_channels, input_injection, browser_automation and code_execution default it to false, so declaring one of those blocks without it configures a guard that never runs. The other 8 default it to true, so declaring the block turns the guard on. remote_desktop_channels.audio is the one side channel that defaults to permitted.
tool_access in a document
rules:
tool_access:
enabled: true
default: block # "allow" or "block"
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 # compiles to Constraint::MaxArgsSize
require_runtime_assurance_tier: attested # none | basic | attested | verified
prefer_runtime_assurance_tier: verified
require_workload_identity:
scheme: spiffe # the only scheme
trust_domain: example.org
path_prefixes: ["/agents/"]
credential_kinds: [x509_svid] # uri | x509_svid | jwt_svidThree of those keys leave the guard pipeline entirely and land on the compiled capability grants instead: max_args_size becomes Constraint::MaxArgsSize, require_runtime_assurance_tier becomes Constraint::MinimumRuntimeAssurance, and require_confirmation forces Constraint::RequireApprovalAbove to a threshold of zero on the grants its pattern matches, the same constraint human_in_loop emits from approve_above. A selective confirmation that would have to be widened to a wildcard grant is not widened; it stays in the policy evaluator. Capabilities defines those constraints and Scope Matching says which of them a matcher can decide.
For worked examples and list semantics per rule, Write a Policy is the operator-facing companion to the schema above.
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: posture states keyed by name, origin profiles keyed by id.
The chio block is the one carried rather than interpreted. Its doc comment says the kernel does not read it, and its five fields (market_hours, signing, k8s_namespaces, rollback, human_in_loop) travel with the document for bridge consumers. One path inside it is an exception: the policy compiler reads extensions.chio.human_in_loop.approvers and, when it is present, refuses to compile through the plain compile_policy entry point at all. Threshold approval needs an authenticated approver directory, and Policy Compilation owns the four refusals it raises without one.
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, and Policy Compilation for which guards each block materializes, the limits on extends resolution, and the bounded static analysis that runs alongside it.
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 and no cross-reference validation tyingextensions.originsto posture states. The compiler records guard names in a parallel list with no uniqueness check. 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