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:

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

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

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

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

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

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

yaml
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

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

bash
# 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:

text
$ 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.

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

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

yaml
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