Chio/Docs

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

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"

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.

FieldTypeRequiredDescription
hushspecstringYesSchema version. Always "0.1.0".
namestringNoHuman-readable identifier carried into receipts.
descriptionstringNoFree-form prose explaining intent.
extendsstringNoReference to a base policy (built-in or filesystem path).
merge_strategyenumNoHow the document folds into its parent. Default deep_merge.
rulesobjectNoPer-guard configuration. The core of the policy.
extensionsobjectNoDomain-specific add-ons: posture, origins, detection, reputation, runtime assurance, chio.
metadataobjectNoGovernance 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:

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

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 — 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).

BlockCompiles to
forbidden_pathsForbiddenPathGuard
path_allowlistPathAllowlistGuard
egressEgressAllowlistGuard
secret_patternsSecretLeakGuard
patch_integrityPatchIntegrityGuard
shell_commandsShellCommandGuard
tool_accessMcpToolGuard + capability grants
computer_useComputerUseGuard
remote_desktop_channelsRemoteDesktopSideChannelGuard
input_injectionInputInjectionCapabilityGuard
browser_automationBrowserAutomationGuard
code_executionCodeExecutionGuard
velocityVelocityGuard (+ AgentVelocityGuard)
human_in_loopConstraint::RequireApprovalAbove on grants

tool_access (Detail)

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

crates/guards/chio-policy/src/models/rules.rs
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 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 (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.

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.

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 — the compiler records guard names in a parallel list with no uniqueness check — and no cross-reference validation tying extensions.origins to posture states. 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.yaml
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