Writing Your First Policy
This guide walks through building a HushSpec policy from scratch, validating it, and testing it against representative actions.
Step 1: Start Minimal
Every HushSpec document needs exactly one required field:
hushspec: "0.1.0"
This is a valid document. It declares no rules, so the evaluator has nothing to restrict.
Step 2: Add Metadata
Give the policy a name and description so it is identifiable in logs, receipts, and reviews:
hushspec: "0.1.0"
name: "my-first-policy"
description: "Starter guardrails for local development"
Step 3: Block Sensitive Paths
The most common first rule is a deny-list for credentials and secrets:
hushspec: "0.1.0"
name: "my-first-policy"
rules:
forbidden_paths:
patterns:
- "**/.ssh/**"
- "**/.aws/**"
- "**/.env"
- "**/credentials*"
exceptions:
- "**/.env.example"
Step 4: Control Network Egress
Next, restrict which domains the agent can reach:
rules:
forbidden_paths:
patterns:
- "**/.ssh/**"
- "**/.aws/**"
egress:
allow:
- "api.openai.com"
- "*.anthropic.com"
- "*.googleapis.com"
default: "block"
With default: "block", any domain that does not match the allow list is denied.
Step 5: Add Secret Detection
Catch secrets before they are written to disk or sent elsewhere:
rules:
# ... previous rules ...
secret_patterns:
patterns:
- name: aws_key
pattern: "AKIA[0-9A-Z]{16}"
severity: critical
- name: private_key
pattern: "-----BEGIN (RSA |EC )?PRIVATE KEY-----"
severity: critical
- name: generic_token
pattern: "(?i)(token|secret|password)\\s*[=:]\\s*['\"]?[a-z0-9]{20,}"
severity: warn
Step 6: Control Tool Access
Guard high-risk tools and require confirmation for the ones you still want available:
rules:
# ... previous rules ...
tool_access:
block:
- shell_exec
- run_command
require_confirmation:
- deploy
- database_write
default: "allow"
Step 7: Add Extensions (Optional)
Extensions are optional, but they still have to validate against the published schemas. Engines may choose not to act on an extension they do not implement, but the document shape remains strict.
extensions:
# Posture: state machine plus capability budgets
posture:
initial: standard
states:
restricted:
capabilities: [file_access]
standard:
capabilities: [file_access, file_write, egress]
budgets:
file_writes: 50
transitions:
- from: "*"
to: restricted
on: critical_violation
# Detection: thresholds only, not detection algorithms
detection:
prompt_injection:
enabled: true
warn_at_or_above: suspicious
block_at_or_above: high
See Posture, Origins, and Detection for the full extension contracts.
The Complete Policy
hushspec: "0.1.0"
name: "my-first-policy"
description: "Development policy with basic protections"
rules:
forbidden_paths:
patterns:
- "**/.ssh/**"
- "**/.aws/**"
- "**/.env"
exceptions:
- "**/.env.example"
egress:
allow:
- "api.openai.com"
- "*.anthropic.com"
default: "block"
secret_patterns:
patterns:
- name: aws_key
pattern: "AKIA[0-9A-Z]{16}"
severity: critical
- name: private_key
pattern: "-----BEGIN (RSA |EC )?PRIVATE KEY-----"
severity: critical
shell_commands:
forbidden_patterns:
- "rm\\s+-rf\\s+/"
- "curl.*\\|.*sh"
tool_access:
block:
- shell_exec
require_confirmation:
- deploy
default: "allow"
Validating Your Policy
Before you ship a policy, validate it with the h2h CLI. This catches schema errors, unknown fields, duplicate pattern names, invalid regexes, and, in strict mode, broken extends references.
# Structural validation
h2h validate policy.yaml
# Also resolve extends references
h2h validate --strict policy.yaml
Text output uses a simple pass/fail report with error codes:
$ h2h validate policy.yaml
✓ policy.yaml
$ h2h validate --strict missing-base.yaml
✗ missing-base.yaml
error[E010]: extends resolution failed: Unknown ruleset or file not found: company/base.yaml
You can also validate programmatically from any of the SDKs; see Getting Started for language-specific examples.
Testing Your Policy
h2h test executes evaluator fixtures declared in .test.yaml files. Each fixture embeds a policy and one or more test cases, and each test case supplies an action plus the expected decision.
hushspec_test: "0.1.0"
description: "Starter policy checks"
policy:
hushspec: "0.1.0"
rules:
forbidden_paths:
patterns:
- "**/.ssh/**"
cases:
- description: "SSH private key is denied"
action:
type: "file_read"
target: "/home/user/.ssh/id_rsa"
expect:
decision: "deny"
- description: "Regular source file is allowed"
action:
type: "file_read"
target: "/workspace/src/main.ts"
expect:
decision: "allow"
Run the fixture directly, or override the embedded policy while iterating:
h2h test tests/starter-policy.test.yaml
h2h test --policy policy.yaml --fixtures tests/
Extending Built-in Rulesets
Instead of starting from zero, you can extend one of the built-in rulesets that ship with HushSpec. Use extends to inherit a base policy and override only the rule blocks you want to change:
hushspec: "0.1.0"
name: "my-custom-policy"
extends: "default"
rules:
egress:
allow:
- "api.openai.com"
- "*.internal.company.com"
default: "block"
Current built-ins: default, strict, permissive, ai-agent, cicd, remote-desktop, and panic.
With deep_merge (the default merge strategy), only the rule blocks you define are overridden; the rest of the inherited policy remains intact. See Merge Semantics for the merge rules and Using with Clawdstrike for library and compliance pack examples.
Next Steps
- Read the Rules Reference for the full field-level rule contracts
- Learn Merge Semantics before composing multi-layer policies
- Add Posture, Origins, or Detection only when you need them
- Use the CLI Reference to wire validation and tests into CI
- Deploy the same policy through Clawdstrike or the language SDKs