Skip to content

Policy Syntax Reference

Shield policies are YAML files that define rules for Tier 0 evaluation. Each rule matches against action properties and determines whether the action should be allowed, blocked, or escalated to a higher tier.

File Location

Policies live in the workspace's security/shield/ directory. The active policy is specified in config.yaml:

yaml
shield:
  policy_file: security/shield/default.yaml

OpenParallax ships with three policy presets:

PolicyFilePhilosophy
Defaultsecurity/shield/default.yamlBalanced — allows reads, requires evaluation for writes and commands
Permissivesecurity/shield/permissive.yamlMinimal friction — allows most actions, blocks only dangerous paths
Strictsecurity/shield/strict.yamlMaximum security — requires Tier 2 for any write or command

Schema

yaml
# Top-level structure
version: 1                    # Policy format version (currently always 1)
description: "Policy name"    # Human-readable description

# Default behavior when no rule matches
default:
  decision: ESCALATE          # ALLOW | BLOCK | ESCALATE
  min_tier: 1                 # Minimum tier required (0, 1, or 2)

# Rules are evaluated in order — first match wins
rules:
  - name: rule-name           # Unique identifier (lowercase, hyphens)
    description: "Why"        # Human-readable explanation
    action_types:             # List of action types to match
      - read_file
      - list_directory
    path_patterns:            # Glob patterns for file paths (optional)
      - "src/**"
      - "*.go"
    path_deny_patterns:       # Glob patterns to exclude (optional)
      - "/etc/**"
      - "~/.ssh/**"
    content_patterns:         # Regex patterns matching action content (optional)
      - "rm -rf"
      - "DROP TABLE"
    decision: ALLOW           # ALLOW | BLOCK | ESCALATE
    min_tier: 0               # Minimum tier before this decision applies

Field Reference

version

Always 1. Reserved for future schema evolution.

description

Human-readable description of the policy's purpose. Shown in openparallax status and the web UI.

default

The fallback rule when no explicit rule matches an action.

FieldTypeRequiredDescription
decisionstringyesWhat to do with unmatched actions. Usually ESCALATE (send to higher tiers for evaluation).
min_tierintnoMinimum tier required before the default decision applies. Default 0.

Fail-Closed Default

If you set default.decision: ALLOW, unmatched actions skip all security evaluation. This is dangerous. The recommended default is ESCALATE with min_tier: 1 — unmatched actions go through at least the heuristic/classifier before being allowed.

rules

An ordered list of rules. First match wins — once an action matches a rule, no further rules are evaluated. Order your rules from most specific to most general.

Rule Fields

name (required)

Unique identifier for the rule. Lowercase letters, numbers, and hyphens. Used in audit logs and Shield verdicts to identify which rule matched.

description (optional)

Human-readable explanation of why this rule exists. Included in Shield verdict messages.

action_types (required)

List of action types this rule applies to. Must match values from the Action Types reference.

yaml
# Match specific actions
action_types:
  - read_file
  - list_directory
  - search_files

# Match all actions (use with caution)
action_types:
  - "*"

The wildcard "*" matches all action types. Use it for catch-all rules (typically the last rule before the default).

path_patterns (optional)

Glob patterns that match against the action's file path. If specified, the rule only matches when the action's path matches at least one pattern.

yaml
# Match Go source files
path_patterns:
  - "**/*.go"
  - "**/*.mod"

# Match everything in the workspace
path_patterns:
  - "**"

# Match specific directories
path_patterns:
  - "src/**"
  - "internal/**"

Glob syntax:

  • * — matches any characters within a single path segment
  • ** — matches any number of path segments (including zero)
  • ? — matches a single character
  • [abc] — matches any character in the set
  • {go,rs} — matches any of the alternatives

path_deny_patterns (optional)

Glob patterns that exclude paths from matching. If an action's path matches a deny pattern, the rule does not match — even if it matches a path_patterns entry.

yaml
# Allow reads everywhere except sensitive paths
path_patterns:
  - "**"
path_deny_patterns:
  - "/etc/shadow"
  - "/etc/passwd"
  - "~/.ssh/**"
  - "**/.env"
  - "**/.env.*"
  - "**/credentials*"

Deny patterns are evaluated after allow patterns. Think of it as: "match these paths, but NOT these paths."

content_patterns (optional)

Regular expressions that match against the action's content (arguments, command text, file content being written). If specified, the rule only matches when at least one pattern matches the content.

yaml
# Block destructive commands
content_patterns:
  - "rm\\s+-rf"
  - "DROP\\s+TABLE"
  - "DELETE\\s+FROM.*WHERE\\s+1\\s*=\\s*1"
  - "mkfs"
  - "dd\\s+if="
  - "> /dev/sd"

# Block credential exfiltration attempts
content_patterns:
  - "curl.*\\$\\{?[A-Z_]*KEY"
  - "wget.*api[_-]?key"
  - "echo.*\\$\\{?[A-Z_]*SECRET"

Content patterns use Go's regexp syntax (RE2). They are case-sensitive by default. Use (?i) for case-insensitive matching:

yaml
content_patterns:
  - "(?i)drop\\s+table"   # matches DROP TABLE, Drop Table, etc.

decision (required)

What Shield should do when this rule matches:

DecisionEffect
ALLOWAction is permitted. Skips higher tiers (subject to min_tier).
BLOCKAction is denied. No execution, no higher tier evaluation. Immediate and final.
ESCALATEPass to the next tier for further evaluation.

min_tier (optional)

Minimum tier that must have evaluated the action before this rule's decision applies. Default 0 (Tier 0 can make the decision).

ValueMeaning
0Tier 0 (this policy rule) can make the final decision.
1Even if this rule matches, the action must also pass Tier 1 (classifier + heuristic) before the decision is applied. If this rule says ALLOW with min_tier 1, the action is escalated to Tier 1 first.
2Action must pass Tier 2 (LLM evaluator) regardless of this rule's decision.

This is crucial for defense in depth. Even if a policy rule allows an action, min_tier: 1 ensures the classifier still checks for prompt injection:

yaml
# Allow shell commands, but require classifier check
- name: allow-shell-with-check
  action_types: [run_command]
  decision: ALLOW
  min_tier: 1    # Classifier must also approve

Evaluation Flow

Action arrives at Tier 0

For each rule (in order):
  1. Does action_type match? No → next rule
  2. Does path match path_patterns? No → next rule
  3. Does path match path_deny_patterns? Yes → next rule
  4. Does content match content_patterns? No → next rule
  5. Match found!

  Is decision BLOCK? → BLOCK immediately
  Is decision ALLOW and min_tier ≤ 0? → ALLOW immediately
  Is decision ALLOW but min_tier > 0? → ESCALATE to next tier
  Is decision ESCALATE? → ESCALATE to next tier

No rule matched → apply default

Examples

Default Policy (Balanced)

yaml
version: 1
description: "Balanced security — allows reads, evaluates writes and commands"

default:
  decision: ESCALATE
  min_tier: 1

rules:
  # Read operations are always allowed
  - name: allow-reads
    description: "File reads and searches are safe"
    action_types:
      - read_file
      - list_directory
      - search_files
      - git_status
      - git_diff
      - git_log
      - memory_search
      - read_email
      - search_email
      - read_calendar
      - list_tasks
    decision: ALLOW
    min_tier: 0

  # Block access to sensitive system paths
  - name: block-system-paths
    description: "Never access system-critical files"
    action_types: ["*"]
    path_patterns:
      - "/etc/shadow"
      - "/etc/passwd"
      - "~/.ssh/**"
      - "**/.env"
      - "**/.env.*"
      - "**/credentials*"
      - "**/*secret*"
    decision: BLOCK

  # Block destructive commands
  - name: block-destructive
    description: "Block known-dangerous commands"
    action_types: [run_command]
    content_patterns:
      - "rm\\s+-rf\\s+/"
      - "mkfs"
      - "dd\\s+if="
      - "> /dev/sd"
      - "chmod\\s+777"
      - "curl.*\\|.*sh"
    decision: BLOCK

  # Browser navigation — allow with classifier check
  - name: allow-browser
    action_types:
      - browser_navigate
      - browser_extract
    decision: ALLOW
    min_tier: 1

  # Shell commands — require classifier
  - name: shell-needs-classifier
    action_types: [run_command]
    decision: ALLOW
    min_tier: 1

  # File writes — require classifier
  - name: writes-need-classifier
    action_types:
      - write_file
      - edit_file
      - delete_file
      - move_file
    decision: ALLOW
    min_tier: 1

  # Email sending — require Tier 2 (LLM evaluator)
  - name: email-needs-evaluator
    description: "Sending email is high-risk — require human-level evaluation"
    action_types: [send_email]
    decision: ALLOW
    min_tier: 2

Strict Policy

yaml
version: 1
description: "Maximum security — Tier 2 for all writes and commands"

default:
  decision: BLOCK

rules:
  - name: allow-reads
    action_types:
      - read_file
      - list_directory
      - search_files
      - git_status
      - git_diff
      - git_log
      - memory_search
    decision: ALLOW
    min_tier: 0

  - name: all-writes-tier2
    action_types: ["*"]
    decision: ALLOW
    min_tier: 2

Permissive Policy

yaml
version: 1
description: "Minimal friction — allows most actions, blocks only dangerous paths"

default:
  decision: ALLOW
  min_tier: 1

rules:
  - name: allow-reads
    action_types:
      - read_file
      - list_directory
      - search_files
      - git_status
      - git_diff
      - git_log
    decision: ALLOW
    min_tier: 0

  - name: block-system
    action_types: ["*"]
    path_patterns:
      - "/etc/**"
      - "~/.ssh/**"
    decision: BLOCK

Testing Policies

You can test a policy without running the full agent:

bash
# Dry-run: check what the policy would do for a specific action
openparallax shield test \
  --policy security/shield/default.yaml \
  --action read_file \
  --path src/main.go

# Output: ALLOW (rule: allow-reads, tier: 0)
bash
# Test a dangerous action
openparallax shield test \
  --policy security/shield/default.yaml \
  --action run_command \
  --content "rm -rf /"

# Output: BLOCK (rule: block-destructive)

Best Practices

  1. Start with the default policy. It's balanced and covers common cases. Customize from there.
  2. Put BLOCK rules before ALLOW rules. First match wins — block dangerous paths before allowing general reads.
  3. Use min_tier for defense in depth. Even if a rule allows an action, requiring Tier 1 ensures the classifier checks for prompt injection.
  4. Be specific with path_deny_patterns. Broad deny patterns can accidentally block legitimate workspace files.
  5. Use content patterns for commands. Path patterns don't help with run_command — the danger is in the command text, not a file path.
  6. Test after changes. Use openparallax shield test to verify your rules match as expected.
  7. Keep the default as ESCALATE. Setting default.decision: ALLOW bypasses security for unmatched actions. Only use in development.