Chio/Docs
LOGIN · JOIN

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

HushSpec behavior is defined by 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, 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:

yaml
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.

FieldTypeRequiredWhat it carries
hushspecStringYesSchema version. Checked in validate(), not at format detection.
nameOption<String>NoHuman-readable identifier carried into receipts.
descriptionOption<String>NoFree-form prose explaining intent.
extendsOption<String>NoReference to a base policy: a built-in ruleset name or a filesystem path.
merge_strategyOption<MergeStrategy>NoHow this document folds into its parent. Defaults to deep_merge.
rulesOption<Rules>NoPer-guard configuration. The core of the policy.
extensionsOption<Extensions>NoDomain-specific add-ons: posture, origins, detection, reputation, runtime assurance, and the chio overlay.
metadataOption<GovernanceMetadata>NoGovernance 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:

ReferenceLoaderExample
Built-in rulesetEmbedded YAMLchio:ai-agent, chio:cicd, chio:default, chio:panic, chio:permissive, chio:remote-desktop, chio:strict
Filesystem pathReads 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

The composite loader explicitly rejects 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:

StrategyBehavior
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.
mergeTop-level shallow merge: child blocks fully replace any same-named parent block but other top-level blocks are preserved.
replaceChild 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.

BlockStructCompiles to
forbidden_pathsForbiddenPathsRuleForbiddenPathGuard
path_allowlistPathAllowlistRulePathAllowlistGuard
egressEgressRuleEgressAllowlistGuard, plus InternalNetworkGuard as its SSRF companion
secret_patternsSecretPatternsRuleSecretLeakGuard on the write path, plus a post-invocation SanitizerHook on the read path
patch_integrityPatchIntegrityRulePatchIntegrityGuard
shell_commandsShellCommandsRuleShellCommandGuard
tool_accessToolAccessRuleMcpToolGuard, plus the capability grants and their MaxArgsSize and MinimumRuntimeAssurance constraints
computer_useComputerUseRuleComputerUseGuard
remote_desktop_channelsRemoteDesktopChannelsRuleRemoteDesktopSideChannelGuard
input_injectionInputInjectionRuleInputInjectionCapabilityGuard
browser_automationBrowserAutomationRuleBrowserAutomationGuard
code_executionCodeExecutionRuleCodeExecutionGuard
velocityVelocityRuleVelocityGuard and AgentVelocityGuard, both threaded with the configured process memory budget
human_in_loopHumanInLoopRuleConstraint::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.

BlockFieldTypeDefault
forbidden_pathsenabledbooltrue
forbidden_pathspatternsVec<String>empty
forbidden_pathsexceptionsVec<String>empty
path_allowlistenabledboolfalse
path_allowlistreadVec<String>empty
path_allowlistwriteVec<String>empty
path_allowlistpatchVec<String>empty
egressenabledbooltrue
egressallowVec<String>empty
egressblockVec<String>empty
egressdefaultDefaultActionblock
secret_patternsenabledbooltrue
secret_patternspatternsVec<SecretPattern>empty
secret_patternsskip_pathsVec<String>empty
patch_integrityenabledbooltrue
patch_integritymax_additionsusize1000
patch_integritymax_deletionsusize500
patch_integrityforbidden_patternsVec<String>empty
patch_integrityrequire_balanceboolfalse
patch_integritymax_imbalance_ratiof6410.0
shell_commandsenabledbooltrue
shell_commandsforbidden_patternsVec<String>empty
tool_accessenabledbooltrue
tool_accessallowVec<String>empty
tool_accessblockVec<String>empty
tool_accessrequire_confirmationVec<String>empty
tool_accessdefaultDefaultActionallow
tool_accessmax_args_sizeOption<usize>None
tool_accessrequire_runtime_assurance_tierOption<RuntimeAssuranceTier>None
tool_accessprefer_runtime_assurance_tierOption<RuntimeAssuranceTier>None
tool_accessrequire_workload_identityOption<WorkloadIdentityMatch>None
tool_accessprefer_workload_identityOption<WorkloadIdentityMatch>None
computer_useenabledboolfalse
computer_usemodeComputerUseModeguardrail
computer_useallowed_actionsVec<String>empty
remote_desktop_channelsenabledboolfalse
remote_desktop_channelsclipboardboolfalse
remote_desktop_channelsfile_transferboolfalse
remote_desktop_channelsaudiobooltrue
remote_desktop_channelsdrive_mappingboolfalse
input_injectionenabledboolfalse
input_injectionallowed_typesVec<String>empty
input_injectionrequire_postcondition_probeboolfalse
browser_automationenabledboolfalse
browser_automationallowed_domainsVec<String>empty
browser_automationblocked_domainsVec<String>empty
browser_automationallowed_verbsVec<String>empty
browser_automationcredential_detectionbooltrue
browser_automationextra_credential_patternsVec<String>empty
code_executionenabledboolfalse
code_executionlanguage_allowlistVec<String>empty
code_executionmodule_denylistVec<String>empty
code_executionnetwork_accessboolfalse
code_executionmax_execution_time_msOption<u64>None
code_executionmax_scan_bytesOption<usize>None
velocityenabledbooltrue
velocitymax_invocations_per_windowOption<u32>None
velocitymax_spend_per_windowOption<u64>None
velocitymax_requests_per_agentOption<u32>None
velocitymax_requests_per_sessionOption<u32>None
velocitywindow_secsu6460
velocityburst_factorf641.0
human_in_loopenabledbooltrue
human_in_looprequire_confirmationVec<String>empty
human_in_loopapprove_aboveOption<u64>None
human_in_loopapprove_above_currencyOption<String>None
human_in_looptimeout_secondsOption<u64>None
human_in_loopon_timeoutHumanInLoopTimeoutActiondeny

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

yaml
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_svid

Three 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 optional TimeWindowCondition (start, end, optional timezone and days-of-week) matched against the request time.
  • context: an optional key/value map matched against the RuntimeContext maps (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.

ExtensionPurpose
postureStateful posture machine: initial state, state-specific capabilities/budgets, transitions on triggers.
originsPer-origin profiles with their own tool_access and egress overrides; default behavior is deny or minimal_profile.
detectionPrompt-injection, jailbreak, and threat-intel detectors (regex and similarity-based).
reputationReputation tiers, scoring weights, promotion/demotion triggers and metrics requirements.
runtime_assuranceTier requirements and verifier rules sourced from chio_core::appraisal.
chioChio-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.

FieldTypeNotes
authorstringFree-form.
approved_bystringFree-form.
approval_datestringRFC 3339 recommended.
classificationenumpublic | internal | confidential | restricted
change_ticketstringReference to your change-management system.
lifecycle_stateenumdraft | review | approved | deployed | deprecated | archived
policy_versionintInternal version counter (separate from hushspec).
effective_datestringWhen the policy starts applying.
expiry_datestringWhen the policy expires.

Compile model

Compilation runs in three stages:

  1. Parse. HushSpec::parse reads the YAML, applies pre-checks against malformed scalars, and deserializes with deny_unknown_fields.
  2. Resolve and merge. resolve_with_loader walks the extends chain, detecting cycles, then folds children over parents per merge_strategy.
  3. Compile. compile_policy turns the resolved HushSpec into a CompiledPolicy: a guard pipeline plus initial capability grants. A separate control-plane path, build_guard_pipeline / build_post_invocation_pipeline (re-exported from chio_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_fields applies to HushSpec, Rules, every rule struct, and every extension. Typos like forbidden_path (singular) instead of forbidden_paths are 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 extends are rejected. The resolver returns ResolveError::Cycle with the chain.
  • HTTP references are rejected. The composite loader returns ResolveError::Http for http:// and https:// references.
  • Regex patterns are validated. shell_commands.forbidden_patterns,patch_integrity.forbidden_patterns, and secret_patterns.patterns[].pattern go through chio_policy::regex_safety and reject pathological patterns.
  • Default actions are enums. tool_access.default and egress.default accept only allow or block. Other strings fail to deserialize.
  • Posture references are validated. validate_posture checks that posture.initial and every posture.transitions[].from/to name a state defined in posture.states, and that no transition targets "*". There is no duplicate-guard-name rejection and no cross-reference validation tying extensions.origins to posture states. The compiler records guard names in a parallel list with no uniqueness check. Required-field checks on external-guard providers (a non-empty api_key or endpoint) 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.

team-policy.yamlyaml
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_guardrails and threat_intel), separate from HushSpec rules