HushSpec policy patterns
A block-by-default policy you can test call by call, with the receipt naming the rule behind every refusal.
On this page
Write a policy that admits only the tools and paths your agent needs, then prove each boundary with chio check before an agent runs under it.
HushSpec is one of the two policy formats chio loads: a file with a top-level hushspec key and a rules block. The starter format in Write a Policy uses kernel, guards, and capabilities sections instead, so keep the two in separate files.
1. Start from a block-by-default document
Create policy.yaml with the version line, a name, and one block per guard under rules. Every check below runs against this file.
hushspec: "0.1.0"
name: my-policy
description: What this policy is for.
rules:
# One entry per guard. Omitted guards are disabled.
tool_access:
enabled: true
default: block
allow:
- read_file
forbidden_paths:
enabled: true
patterns:
- "**/.env"
- "**/.ssh/**"
# ... path_allowlist, egress, shell_commands, secret_patterns,
# patch_integrity, velocity, etc.HushSpec validates strictly with deny_unknown_fields. The top level accepts only hushspec, name, description, extends, merge_strategy, rules, extensions, and metadata, and hushspec is required. A rule block you omit is disabled and returns allow; a policy with no rules block is valid and restricts nothing.
For a team baseline, extends builds on a base policy and merge_strategy (replace, merge, or deep_merge) sets how the overlay folds in. Keep shared forbidden_paths and secret_patterns in the base and project rules in the overlay. Version policies and review changes like code; metadata carries governance fields such as author, approved_by, and lifecycle_state.
2. Choose the rule blocks your agent needs
rules accepts exactly fourteen keys, so a typo or an invented block name fails validation. Every block has an enabled field and compiles to one or more guards.
| Rule block | Compiled guard | What it checks |
|---|---|---|
forbidden_paths | forbidden-path | File patterns that are always refused |
path_allowlist | path-allowlist | Readable, writable, and patchable trees |
egress | egress-allowlist, internal-network | Outbound domains; private and metadata addresses |
secret_patterns | secret-leak, SanitizerHook | Secrets in writes and in returned results |
patch_integrity | patch-integrity | Patch size, content, and balance |
shell_commands | shell-command | Forbidden command patterns |
tool_access | mcp-tool | Which tools, and argument size |
computer_use | computer-use | Desktop-control actions |
remote_desktop_channels | remote-desktop-side-channel | Clipboard, file transfer, audio, drive mapping |
input_injection | input-injection-capability | Synthetic keyboard and mouse input |
browser_automation | browser-automation | Browser domains and verbs |
code_execution | code-execution | Languages, modules, network, run time |
velocity | velocity, agent-velocity | Request and planned-spend rates |
human_in_loop | RequireApprovalAbove constraint | Approval before matched calls |
Two guards arrive without a key of their own. Enabling egress also attaches internal-network, an SSRF companion that blocks RFC1918 and cloud-metadata endpoints, and no rules.internal_network key exists to configure it. Per-agent rate limiting, agent-velocity, derives from velocity and from origin budgets under extensions.origins.
Fields, defaults, and an example for each block are in Pattern policies and rule block fields.
3. Layer rules so every enabled guard must allow the call
Guards combine conjunctively: every enabled guard must allow a request, and one deny refuses the whole call. The capability token is a separate check, so a call succeeds only with a grant that covers it and a policy that allows it. Build up from the restrictive end:
- Tools. Set
tool_access.default: blockand allow only the tools the agent uses. Underdefault: allow, any new tool the MCP server exposes passes this guard unless another rule blocks it. - Directories. Enable
path_allowlistwith the narrowest tree that covers the workflow. - Secrets on disk. Add
forbidden_pathsfor.env,.ssh, and.pemfiles inside that tree. A forbidden match refuses a path even when the allowlist admits it. - Network. Allow only the domains the workflow calls;
egressblocks every other domain. - Depth. Add
shell_commands,secret_patterns,patch_integrity, andvelocity. A velocity limit stops a looping agent from exhausting resources or budgets, andsecret-leakcatches credentials a misconfigured upstream server exposes.
Guards are independent, so disabling one leaves the others unchanged. Start with tool_access and add guards as requirements clarify. When a call is refused in production, find out why: the allowlist may be too narrow, or it stopped a call nobody authorized.
Read more in Capabilities, which covers the token side of each call.
4. Check one call against the policy
chio check evaluates a single tool call against a policy without starting a server. Admission is durable, so the command records the operation in an admission database and the decision in a receipt database.
chio --session-db <admission.db> --receipt-db <receipts.db> \
check --policy ./policy.yaml \
--tool <tool-name> \
--params '<json-arguments>'Run an allowed read first: a workspace file that tool_access permits and forbidden_paths does not match.
$ chio --session-db ./admission.db --receipt-db ./receipts.db \
check --policy ./policy.yaml --tool read_file \
--params '{"path": "./workspace/README.md"}'verdict: ALLOW tool: read_file server: * receipt_id: 42044682fff6ecae3f0780888db59410364a475010f20c69f5328f915d63ee26 policy: 66f1f496f9d4305dfd19e0b6269a51ddcd0c3fc4cd3b2af6cadf3eba4413e09c source: aaca00de9bc252e43a089ce0d4e0b6edf813d61fd6712d86915a4dffd917baec mode: preflight fixture: false
You should see ALLOW, a receipt ID, the policy and source hashes, and two lines that print on every run. mode defaults to preflight, which runs the pre-invocation guards only; fixture says whether an output fixture was supplied. The command exits 0 on an allow and 2 on a deny, so a script can assert on the exit code.
Then check a tool outside tool_access.allow, and a path that matches forbidden_paths.
$ chio --session-db ./admission.db --receipt-db ./receipts.db \
check --policy ./policy.yaml --tool write_file \
--params '{"path": "./workspace/out.txt", "content": "test"}'verdict: DENY tool: write_file server: * reason: requested tool write_file on server * is not in capability scope receipt_id: 40db6902b2a0e924bb6c9c5233317557d75a2ec838800c622c4a7d43e4296315 policy: 66f1f496f9d4305dfd19e0b6269a51ddcd0c3fc4cd3b2af6cadf3eba4413e09c source: aaca00de9bc252e43a089ce0d4e0b6edf813d61fd6712d86915a4dffd917baec mode: preflight fixture: false
$ chio --session-db ./admission.db --receipt-db ./receipts.db \
check --policy ./policy.yaml --tool read_file \
--params '{"path": "./workspace/.env"}'verdict: DENY tool: read_file server: * reason: guard denied the request: guard "guard-pipeline" denied the request receipt_id: 4b70e17c82a2097a9ff56e347b278425cefa8daa9a016852a8f8d2ff99f586cf policy: 66f1f496f9d4305dfd19e0b6269a51ddcd0c3fc4cd3b2af6cadf3eba4413e09c source: aaca00de9bc252e43a089ce0d4e0b6edf813d61fd6712d86915a4dffd917baec mode: preflight fixture: false
You should see two refusals with different reasons. The tool never reaches the guards: capability selection refuses it, and the reason says it is not in capability scope. The forbidden path reaches the guards, and the pipeline refuses it.
5. Confirm what the policy still leaves reachable
This policy carries forbidden_paths and no path_allowlist. Check a path that matches none of the forbidden patterns.
$ chio --session-db ./admission.db --receipt-db ./receipts.db \
check --policy ./policy.yaml --tool read_file \
--params '{"path": "/etc/shadow"}'verdict: ALLOW tool: read_file server: * receipt_id: 766ad03fcc0dc8e3f03ad484f896cf1969624cf79e833458b1217d29d9d01eb6 policy: 66f1f496f9d4305dfd19e0b6269a51ddcd0c3fc4cd3b2af6cadf3eba4413e09c source: aaca00de9bc252e43a089ce0d4e0b6edf813d61fd6712d86915a4dffd917baec mode: preflight fixture: false
You should see ALLOW for /etc/shadow. forbidden_paths subtracts from whatever is reachable, and path_allowlist decides what is reachable, so add the allowlist when the agent belongs in one directory.
Keep the same two databases for the whole checklist. Each invocation receives a new request identity, so a previous check never prevents the next decision.
$ chio --session-db ./admission.db --receipt-db ./receipts.db \
check --policy ./policy.yaml --tool read_file \
--params '{"path": "./workspace/notes.md"}'verdict: ALLOW tool: read_file server: * receipt_id: 1950f7e09037848745f36803b29a45cfaee325f00bdc60d45aea088fda77680c policy: 66f1f496f9d4305dfd19e0b6269a51ddcd0c3fc4cd3b2af6cadf3eba4413e09c source: aaca00de9bc252e43a089ce0d4e0b6edf813d61fd6712d86915a4dffd917baec mode: preflight fixture: false
You should see another ALLOW, and the receipt log now holds each independent decision. Keep admission and receipt storage in separate files; they serve different roles. Script these checks and run them in CI, so every policy change is tested before it deploys.
6. Read which rule refused each call in its receipt
A guard refusal's reason line always names guard-pipeline, the one guard the CLI registers for the whole policy, whichever rule block refused. The rule that fired is recorded in the receipt.
not in scope: stops at core stage 3, scope. Request a grant that covers the tool. A policy change cannot add it, because guards only narrow a grant.
In Rust · evaluate.rs:40021 lines
// Step 2: subject binding.
if verified.subject_hex != input.request.agent_id {
// … SubjectMismatch { expected, actual }
return deny(core_err, None, Some(verified));
}
// Step 3: scope match.
let matched_grant_index = match resolve_matched_grant_index(&verified.scope, input.request) {
Ok(index) => index,
Err(error) => return deny(error, None, Some(verified)),
};
// Step 4: guard pipeline.
// … each guard in order; a deny returns GuardDenied, an error GuardError
// Step 5: mutate delegated sibling budget only after every deny-capable
// local check has passed.
if let Err(error) = admit_delegated_budget(input.capability, budgets) {
let core_err = KernelCoreError::InvalidCapability(error);
return deny(core_err, Some(matched_grant_index), Some(verified));
}
List the refusals from the receipt store and pull out each one's guard evidence.
$ chio --receipt-db ./receipts.db receipt list --admin-all \
| jq -c 'select(.decision.verdict == "deny")
$ | {tool: .tool_name, path: .action.parameters.path,
$ reason: .decision.reason, guard: .decision.guard,
$ evidence: [.evidence[]?.guard_name]}'{"tool":"write_file","path":"./workspace/out.txt","reason":"requested tool write_file on server * is not in capability scope","guard":"kernel","evidence":[]}
{"tool":"read_file","path":"./workspace/.env","reason":"guard denied the request: guard \"guard-pipeline\" denied the request","guard":"kernel","evidence":["forbidden-path"]}You should see two receipts. The scope refusal carries no evidence, because no guard ran. The path refusal carries forbidden-path in evidence[].guard_name, the compiled kebab-case guard name rather than the snake_case rule key. Both set decision.guard to kernel, which names the component that decided.
A malformed policy fails at load and prints the accepted keys
HushSpec parses with deny_unknown_fields at every level, so a wrong key stops the load instead of being ignored, and the error lists the accepted set. A block name that is not one of the fourteen:
$ chio policy analyze ./bad-block.yamlpolicy analysis failed: failed to load ./bad-block.yaml: failed to parse HushSpec document at /private~/chio/bad-block.yaml: rules: unknown field `payments_write`, expected one of `forbidden_paths`, `path_allowlist`, `egress`, `secret_patterns`, `patch_integrity`, `shell_commands`, `tool_access`, `computer_use`, `remote_desktop_channels`, `input_injection`, `browser_automation`, `code_execution`, `velocity`, `human_in_loop` at line 4 column 3
A field the block does not define:
$ chio policy analyze ./bad-field.yamlpolicy analysis failed: failed to load ./bad-field.yaml: failed to parse HushSpec document at /private~/chio/bad-field.yaml: rules.tool_access: unknown field `deny`, expected one of `enabled`, `allow`, `block`, `require_confirmation`, `default`, `max_args_size`, `require_runtime_assurance_tier`, `prefer_runtime_assurance_tier`, `require_workload_identity`, `prefer_workload_identity` at line 6 column 5
chio policy analyze loads and resolves a policy without evaluating a call, which makes it the cheapest parse check in CI. Run it on the corrected policy.yaml:
chio policy analyze ./policy.yamlpolicy_sha256 9c41…e07b ID SEVERITY KIND BLOCK RULE summary: 0 error(s), 0 warning(s), 0 notice(s)
You should see the hash of the resolved document and no findings. A block it cannot reason about statically, such as a velocity window, appears as a not_analyzed notice, which does not fail the run. The remaining errors come from chio check itself.
durable admission mode requires a database. No database flags were passed. Pass both--session-dband--receipt-db.durable receipt persistence unavailable. Only--session-dbwas passed, so every verdict is a deny. Add--receipt-db;--revocation-dband--budget-dbconflict with durable admission.request id conflicts with retained operation. One request identity was reused for a different retained operation. Preserve the retained operation and diagnose the reused identity before retrying an effect; independent checks receive fresh identities.preflight cannot evaluate post-output guards. The policy enablessecret_patternsorpatch_integrity. Add--mode full --output-fixture <path-to-json-file>; the flag takes a path to a JSON file holding the output to evaluate, never inline JSON.- An unexpected deny with
path-allowlistin the evidence.chio checkopens a session that declares no filesystem roots, and the guard matches nothing against an empty root set. Exercise apath_allowlistpolicy through a live edge, where the client supplies roots.
Captured output for the two database errors
$ chio check --policy ./policy.yaml --tool read_file \
--params '{"path": "./workspace/README.md"}'error[CHIO-CLI-ADMISSION-STORE-REQUIRED]: durable admission needs a session database so operations and tool outcomes survive a restart help: pass --session-db <path> code: urn:chio:error:cli:admission-store-required docs: https://chio.computer/docs/reference/errors#error-cli-admission-store-required
$ chio --session-db ./admission.db \
check --policy ./policy.yaml --tool read_file \
--params '{"path": "./workspace/README.md"}'verdict: DENY tool: read_file server: * reason: internal error: durable receipt persistence unavailable: no receipt store configured receipt_id: 7ba0d1ef263cd0790eaa68e9c3dc307e8d1ec387dd2decb1d7a93ffc30852f59 policy: 66f1f496f9d4305dfd19e0b6269a51ddcd0c3fc4cd3b2af6cadf3eba4413e09c source: aaca00de9bc252e43a089ce0d4e0b6edf813d61fd6712d86915a4dffd917baec mode: preflight fixture: false
Pattern policies and rule block fields
Try production, egress, or max_args_size. Each entry has its own link.
Read-only workspace
An agent that reviews a codebase, summarizes a directory tree, or answers questions over local files. No writes, no shell, no network.
hushspec: "0.1.0"
name: readonly-workspace
description: Read-only access to a project workspace.
rules:
tool_access:
enabled: true
default: block
allow:
- read_file
- list_directory
- search_files
path_allowlist:
enabled: true
read:
- "./workspace/**"
write: []
patch: []
forbidden_paths:
enabled: true
patterns:
- "**/.env"
- "**/.env.*"
- "**/*.pem"
- "**/*.key"
- "**/.ssh/**"
shell_commands:
enabled: true
forbidden_patterns:
- ".*"
egress:
enabled: true
allow: []
secret_patterns:
enabled: true
patch_integrity:
enabled: true
velocity:
enabled: true
max_invocations_per_window: 200
window_secs: 120API only, with no filesystem
An agent that talks to GitHub, OpenAI, and Anthropic and touches no local file. The filesystem is closed; egress is open to three named hosts.
hushspec: "0.1.0"
name: api-only
description: Network API access with no filesystem operations.
rules:
tool_access:
enabled: true
default: block
allow:
- fetch
- search_repositories
- create_issue
path_allowlist:
enabled: true
read: []
write: []
patch: []
forbidden_paths:
enabled: true
patterns:
- "**/*"
egress:
enabled: true
allow:
- "api.github.com"
- "*.openai.com"
- "api.anthropic.com"
shell_commands:
enabled: true
forbidden_patterns:
- ".*"
secret_patterns:
enabled: true
patterns:
- name: bearer_token
pattern: 'Bearer\s+[A-Za-z0-9\-._~+/]+=*'
severity: critical
patch_integrity:
enabled: true
velocity:
enabled: true
max_invocations_per_window: 100
window_secs: 60Development with named tools
A broader local policy for named development tools. It allows workspace edits and mediated network calls, adds explicit secret and command checks, and requires approval for deletion. Test the classified actions your adapter submits: pattern checks neither sandbox a process nor recognize every equivalent command.
hushspec: "0.1.0"
name: development
description: Local development policy with explicit tools and approval for deletion.
rules:
tool_access:
enabled: true
default: block
allow:
- read_file
- list_directory
- write_file
- apply_patch
- delete_file
- execute_command
- fetch
block:
- execute_command_as_root
require_confirmation:
- delete_file
path_allowlist:
enabled: true
read:
- "./**"
write:
- "./**"
patch:
- "./**"
forbidden_paths:
enabled: true
patterns:
- "**/.ssh/id_*"
- "**/.aws/credentials"
- "**/.env.production"
shell_commands:
enabled: true
forbidden_patterns:
- '(?i)rm\s+-rf\s+/'
- '(?i)\bchmod\s+777\b'
- '(?i)curl.*\|.*bash'
egress:
enabled: true
default: allow
block:
- "*.malware.com"
secret_patterns:
enabled: true
patch_integrity:
enabled: true
max_additions: 2000
max_deletions: 1000
velocity:
enabled: true
max_invocations_per_window: 500
window_secs: 60Production lockdown
An agent running unattended against production data: two tools, one directory, no admitted shell or egress actions, and a 60-token invocation bucket refilling at one token per second. Apply it on the host's mediated paths; isolation comes from the host.
hushspec: "0.1.0"
name: production-lockdown
description: Maximum restriction for production deployments.
rules:
tool_access:
enabled: true
default: block
allow:
- read_file
- list_directory
max_args_size: 2048
path_allowlist:
enabled: true
read:
- "/app/data/**"
write: []
patch: []
forbidden_paths:
enabled: true
patterns:
- "**/.env*"
- "**/*.pem"
- "**/*.key"
- "**/.ssh/**"
- "**/.aws/**"
- "**/.gcloud/**"
- "**/credentials*"
- "**/secrets*"
shell_commands:
enabled: true
forbidden_patterns:
- ".*"
egress:
enabled: true
allow: []
secret_patterns:
enabled: true
patterns:
- name: aws_key
pattern: "AKIA[0-9A-Z]{16}"
severity: critical
- name: private_key
pattern: "-----BEGIN (RSA |EC |DSA )?PRIVATE KEY-----"
severity: critical
- name: jwt
pattern: 'eyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.'
severity: error
patch_integrity:
enabled: true
max_additions: 200
max_deletions: 100
forbidden_patterns:
- 'eval\('
- 'Function\('
- '__import__\('
require_balance: true
max_imbalance_ratio: 3.0
velocity:
enabled: true
max_invocations_per_window: 60
window_secs: 60Top-level fields
| Field | Required | Description |
|---|---|---|
hushspec | Yes | Schema version, always "0.1.0" |
name | No | Human-readable name for the policy |
description | No | The policy's intent |
extends | No | Base policy plus an overlay; see merge_strategy |
merge_strategy | No | replace, merge, or deep_merge |
rules | No | Guard configurations |
mcp-tool (tool_access)
Controls which tools an agent can invoke. With default: allow, a new tool passes this guard unless another rule blocks it; the caller still needs a matching capability and must pass the other checks.
rules:
tool_access:
enabled: true
default: block # "block" or "allow"
allow: # Tools explicitly permitted
- read_file
- list_directory
- search_files
block: # Tools explicitly denied (overrides allow)
- delete_file
- execute_command
require_confirmation: # Tools that need human approval
- write_file
max_args_size: 4096 # Max size of tool arguments in bytes| Field | Type | Default | Description |
|---|---|---|---|
enabled | bool | true | Whether this guard is active. |
default | string | allow | Action for tools in neither list. |
allow | list | [] | Tool names explicitly allowed. |
block | list | [] | Tool names explicitly blocked; overrides allow. |
require_ | list | [] | Tools that require human approval. |
max_args_size | int | none | Maximum serialized argument size, in bytes. |
path-allowlist
Restricts filesystem access to declared directory trees, with separate lists for read, write, and patch operations. Enabled with empty lists, it refuses every filesystem request the policy evaluates. The host routes the relevant operations through Chio and supplies operating-system isolation.
rules:
path_allowlist:
enabled: true
read: # Paths the agent can read from
- "./workspace/**"
- "./config/settings.json"
write: # Paths the agent can write to
- "./workspace/output/**"
patch: # Paths the agent can apply patches to
- "./workspace/src/**"| Field | Type | Default | Description |
|---|---|---|---|
enabled | bool | false | Whether this guard is active. |
read | list | [] | Glob patterns for readable paths. |
write | list | [] | Glob patterns for writable paths. |
patch | list | [] | Glob patterns for patchable paths. |
forbidden-path
Refuses specific file patterns regardless of the allowlist. Forbidden paths take precedence: a path that matches both the allowlist and a forbidden pattern is denied.
rules:
forbidden_paths:
enabled: true
patterns: # Glob patterns that are always denied
- "**/.env"
- "**/.env.*"
- "**/*.pem"
- "**/*.key"
- "**/.ssh/**"
- "**/node_modules/**"
exceptions: # Paths exempt from forbidden patterns
- "/workspace/.ssh/known_hosts"| Field | Type | Default | Description |
|---|---|---|---|
enabled | bool | true | Whether this guard is active. |
patterns | list | [] | Glob patterns that are always denied. |
exceptions | list | [] | Specific paths exempt from forbidden patterns. |
shell-command
Checks any tool call that carries shell command content against the forbidden patterns. Patterns use standard regex syntax; (?i) makes a match case-insensitive.
rules:
shell_commands:
enabled: true
forbidden_patterns: # Regex patterns that cause a deny
- '(?i)rm\s+-rf\s+/'
- '(?i)\b(curl|wget)\b.*\|.*\b(bash|sh)\b'
- '(?i)\bchmod\s+777\b'
- "(?i)\b(DROP|DELETE|TRUNCATE)\b"| Field | Type | Default | Description |
|---|---|---|---|
enabled | bool | true | Whether this guard is active. |
forbidden_ | list | [] | Regex patterns that trigger a deny verdict. |
egress-allowlist
Controls outbound network access. The default action is block, so a domain must be allowed explicitly.
rules:
egress:
enabled: true
default: block # "block" or "allow"
allow: # Domains the agent can reach
- "api.github.com"
- "*.openai.com"
- "registry.npmjs.org"
block: # Domains explicitly denied (overrides allow)
- "evil.com"| Field | Type | Default | Description |
|---|---|---|---|
enabled | bool | true | Whether this guard is active. |
default | string | block | Action for domains in neither list. |
allow | list | [] | Permitted domain patterns; supports * wildcards. |
block | list | [] | Domain patterns explicitly blocked; overrides allow. |
secret-leak
One block, two components with different verdicts. On the write path, SecretLeakGuard scans the content of a file write or patch and denies the call when a pattern matches; every other action passes through (crates/guards/chio-guards/src/secret_leak.rs:335-339). On the read path, a post-invocation SanitizerHook scans the tool result and returns Redact, so the response is rewritten instead of refused (crates/guards/chio-guards/src/post_invocation.rs:106-127). Enabling secret_patterns attaches both (crates/guards/chio-policy/src/compiler/rules.rs:130-146).
Because the hook reads output, a policy with this block cannot be dry-run in the default preflight mode; use --mode full --output-fixture with a JSON file holding the output to evaluate. Without custom patterns, Chio uses built-in detectors for common secret formats such as AWS keys, GitHub tokens, and private keys.
rules:
secret_patterns:
enabled: true
patterns: # Custom secret detection patterns
- name: aws_access_key
pattern: "AKIA[0-9A-Z]{16}"
severity: critical
description: "AWS Access Key ID"
- name: github_token
pattern: "gh[ps]_[A-Za-z0-9_]{36}"
severity: critical
description: "GitHub Personal Access Token"
- name: generic_api_key
pattern: '(?i)(api[_-]?key|apikey)\s*[:=]\s*[''"]?[A-Za-z0-9]{20,}'
severity: warn
skip_paths: # Paths excluded from scanning
- "**/fixtures/**"
- "**/test/data/**"| Field | Type | Default | Description |
|---|---|---|---|
enabled | bool | true | Whether this guard is active. |
patterns | list | [] | Custom patterns: name, pattern (regex), severity, optional description. |
skip_paths | list | [] | Glob patterns for paths excluded from scanning. |
Each pattern's severity is critical, error, or warn. The parser requires it and neither compiler path reads it, so it records intent without changing behavior.
patch-integrity
Checks patches and file modifications: patch size, forbidden content in diffs, and the balance of additions to deletions.
rules:
patch_integrity:
enabled: true
max_additions: 1000 # Max lines added per patch
max_deletions: 500 # Max lines deleted per patch
forbidden_patterns: # Regex patterns forbidden in diffs
- 'eval\('
- 'Function\('
- '__import__\('
require_balance: false # Require additions/deletions to be balanced
max_imbalance_ratio: 10.0 # Max ratio of additions to deletions| Field | Type | Default | Description |
|---|---|---|---|
enabled | bool | true | Whether this guard is active. |
max_additions | int | 1000 | Maximum added lines in a single patch. |
max_deletions | int | 500 | Maximum deleted lines in a single patch. |
forbidden_ | list | [] | Regex patterns that must not appear in diffs. |
require_balance | bool | false | Whether additions and deletions must be roughly balanced. |
max_ | float | 10.0 | Maximum ratio of additions to deletions. |
velocity
Bounds request and planned-spend rates with refillable token buckets. The window sets the refill calculation; it is not a sliding-window counter. max_invocations_per_window is subject to integer quantization, and max_spend_per_window counts planned cost, never the external charges a tool incurs. Rate Limit Guards covers bucket keys, burst capacity, and refill quantization.
rules:
velocity:
enabled: true
max_invocations_per_window: 120 # Input to capacity and refill calculations
window_secs: 60 # Refill period in seconds
max_spend_per_window: 5000 # Planned-cost tokens; requires grant cost metadata
max_requests_per_agent: 120 # Optional per-agent bucket
max_requests_per_session: 480 # Optional per-session bucket
burst_factor: 1.0 # Token-bucket burst multiplier| Field | Type | Default | Description |
|---|---|---|---|
enabled | bool | true | Whether this guard is active. |
max_ | int | none | Base invocation capacity and refill-rate input. |
window_secs | int | 60 | Refill period, in seconds. |
max_ | int | none | Planned-cost bucket input; needs max_ on the matched grant. |
max_ | int | none | Maximum requests attributed to one agent. |
max_ | int | none | Maximum requests within one session. |
burst_factor | float | 1.0 | Token-bucket burst multiplier over the base rate. |
Additional rule blocks
Six more blocks gate interactions beyond files, shell commands, and network access. Each is disabled unless configured and compiles to a dedicated guard.
rules:
# Computer-use / desktop-control action gating.
computer_use:
enabled: true
mode: guardrail # observe | guardrail | fail_closed
allowed_actions:
- screenshot
- left_click
# Remote-desktop side channels. Each channel is off unless listed true;
# audio defaults to true.
remote_desktop_channels:
enabled: true
clipboard: false
file_transfer: false
audio: true
drive_mapping: false
# Synthetic keyboard/mouse input injection.
input_injection:
enabled: true
allowed_types:
- keyboard
require_postcondition_probe: true
# Browser-automation domain and verb gating.
browser_automation:
enabled: true
allowed_domains:
- "pricing.larkfield.example"
blocked_domains:
- "accounts.google.com"
allowed_verbs:
- navigate
- click
credential_detection: true
# Sandboxed code-interpreter restrictions.
code_execution:
enabled: true
language_allowlist:
- python
module_denylist:
- os
- subprocess
network_access: false
max_execution_time_ms: 5000
# Human-in-the-loop approval gating.
human_in_loop:
enabled: true
require_confirmation: # Globs that always require approval
- "deploy_*"
approve_above: 10000 # Cost threshold, integer minor units
approve_above_currency: USD
timeout_seconds: 300
on_timeout: deny # deny | defer| Rule block | Compiled guard | Key fields |
|---|---|---|
computer_use | Computer | mode (observe, guardrail, fail_closed), allowed_actions |
remote_ | Remote | clipboard, file_transfer, audio, drive_mapping |
input_injection | Input | allowed_types, require_ |
browser_ | Browser | allowed_domains, blocked_domains, allowed_verbs, credential_ |
code_execution | Code | language_, module_denylist, network_access, max_ |
human_in_loop | Require constraint | require_, approve_above, approve_, timeout_seconds, on_timeout |
human_in_loop complements tool_access.require_confirmation. Its require_confirmation globs collapse matched tools to an approval threshold of 0, so every call needs sign-off, while approve_above gates only calls whose declared maximum meets or exceeds the threshold. Both compile to a RequireApprovalAbove constraint on the matched tool grants.
Refusal names in source
The kernel formats reason from guard.name() (crates/kernel/chio-kernel/src/kernel/dispatch.rs:400-407), and the CLI registers GuardPipeline, whose name is the literal guard-pipeline (crates/guards/chio-guards/src/pipeline.rs:58-61). The pipeline appends the evidence entry as it short-circuits (crates/guards/chio-guards/src/pipeline.rs:79-87). decision.guard is set to kernel in crates/kernel/chio-kernel/src/kernel/responses/deny_responses.rs:84-86.