Skip to content

Tier 0 — YAML Policy

Tier 0 is the first evaluation tier in the Shield pipeline. It is a deterministic, rule-based policy engine that matches actions against YAML rules using glob patterns. It executes in microseconds with zero external dependencies -- no ML models, no LLM calls, no network requests.

How It Works

When an action arrives, the policy engine checks it against three rule lists in order:

  1. Deny rules -- checked first. If any deny rule matches, the action is immediately BLOCKED. No further evaluation occurs.
  2. Verify rules -- checked second. If a verify rule matches, the action is ESCALATED to the tier specified by tier_override. The action continues to Tier 1 or Tier 2 for further evaluation.
  3. Allow rules -- checked last. If an allow rule matches, the action is ALLOWED (subject to min_tier overrides from other sources).

If no rule matches, the result is NoMatch and the action proceeds to Tier 1.

Action arrives


Deny rules ──── match? ──── YES ──→ BLOCK (immediately)
    │ NO

Verify rules ── match? ──── YES ──→ ESCALATE (to tier_override)
    │ NO

Allow rules ─── match? ──── YES ──→ ALLOW (if minTier ≤ 0)
    │ NO

NoMatch ──────────────────────────→ Proceed to Tier 1

Policy File Structure

A policy file is a YAML document with three top-level keys:

yaml
deny:
  - name: rule_name
    action_types: [...]
    paths: [...]
    tier_override: N

verify:
  - name: rule_name
    action_types: [...]
    paths: [...]
    tier_override: N

allow:
  - name: rule_name
    action_types: [...]
    paths: [...]

Rule Fields

FieldTypeRequiredDescription
namestringyesUnique identifier for the rule. Appears in verdicts and audit logs.
action_typesstring[]noList of action type strings to match. If omitted, matches all action types.
pathsstring[]noList of glob patterns to match against file paths in the action payload. If omitted, matches regardless of path.
tier_overrideintnoFor verify rules: the minimum tier to escalate to (1 or 2). Defaults to 1.

A rule matches when all specified criteria match simultaneously. If a rule has both action_types and paths, the action must match at least one action type AND at least one path pattern.

Pattern Matching

Action Types

Action types are matched by exact string comparison. Shield defines 50+ action types. Common ones:

Action TypeDescription
read_fileRead a file
write_fileWrite/create a file
delete_fileDelete a file
copy_fileCopy a file
move_fileMove/rename a file
list_directoryList directory contents
create_directoryCreate a directory
delete_directoryDelete a directory
execute_commandExecute a shell command
git_commitGit commit
git_pushGit push
send_emailSend an email
send_messageSend a chat message
http_requestMake an HTTP request
memory_writeWrite to memory
memory_searchSearch memory
browser_navigateNavigate browser
canvas_createCreate a canvas file

Path Patterns

Path patterns use glob syntax powered by the gobwas/glob library. The separator is / (forward slash on all platforms). Paths are normalized before matching: tilde (~) is expanded to the user's home directory and backslashes are converted to forward slashes.

PatternMatchesDoes Not Match
~/.ssh/**~/.ssh/id_rsa, ~/.ssh/config, ~/.ssh/keys/deploy~/.ssh (directory itself)
~/.ssh/*~/.ssh/id_rsa, ~/.ssh/config~/.ssh/keys/deploy (nested)
~/.ssh/id_*~/.ssh/id_rsa, ~/.ssh/id_ed25519~/.ssh/config
/etc/shadow/etc/shadow only/etc/shadow.bak
**/SOUL.md./SOUL.md, /home/user/workspace/SOUL.mdSOUL.md.bak
C:\\Windows\\System32\\config\\**Any file under C:\Windows\System32\config\Files elsewhere

Path extraction: Shield extracts paths from multiple payload fields -- path, source, destination, dir, file, and target. This ensures that copy/move operations are caught by both source and destination paths. A rule blocking ~/.ssh/** will catch:

  • read_file with path: ~/.ssh/id_rsa
  • copy_file with source: ~/.ssh/id_rsa or destination: ~/.ssh/id_rsa
  • move_file with source: ~/.ssh/config or destination: ~/.ssh/config

Content Patterns

Content patterns match against the text content within action payloads. They are particularly useful for verify rules that need to inspect what is being written or what command is being executed, beyond just the path or action type.

Examples

Block Access to Sensitive System Paths

yaml
deny:
  - name: block_sensitive_system_paths
    action_types:
      - read_file
      - write_file
      - delete_file
      - copy_file
      - move_file
    paths:
      - "~/.ssh/**"
      - "~/.aws/**"
      - "~/.gnupg/**"
      - "~/.kube/**"
      - "/etc/shadow"
      - "/etc/sudoers"
      - "C:\\Windows\\System32\\config\\**"

This rule blocks any file operation on SSH keys, AWS credentials, GPG keys, Kubernetes config, shadow passwords, sudoers, and Windows security configuration. The action is blocked at Tier 0 -- no ML or LLM evaluation is needed.

Protect Identity Files from Deletion

yaml
deny:
  - name: block_identity_deletion
    action_types:
      - delete_file
      - delete_directory
    paths:
      - "**/SOUL.md"
      - "**/IDENTITY.md"

Identity files define the agent's personality and capabilities. This rule prevents deletion regardless of where they are located (the **/ prefix matches any directory depth).

Require Tier 1 for Shell Commands

yaml
verify:
  - name: evaluate_shell_commands
    action_types:
      - execute_command
    tier_override: 1

Shell commands are powerful and unpredictable. This rule ensures every shell command passes through the Tier 1 classifier (ONNX + heuristic) before execution. If the classifier detects prompt injection or a dangerous pattern, it blocks the command. If it is uncertain, it escalates to Tier 2.

Require Tier 2 for Identity Modification

yaml
verify:
  - name: evaluate_soul_modification
    action_types:
      - write_file
      - copy_file
      - move_file
    paths:
      - "**/SOUL.md"
      - "**/IDENTITY.md"
    tier_override: 2

Modifying identity files is the highest-risk operation -- an attacker who changes the agent's identity can make it do anything. This rule sends identity modifications directly to the LLM evaluator (skipping Tier 1) for deep contextual analysis.

Allow Read-Only Operations

yaml
allow:
  - name: allow_workspace_reads
    action_types:
      - read_file
      - list_directory
      - search_files

  - name: allow_memory_search
    action_types:
      - memory_search

  - name: allow_git_readonly
    action_types:
      - git_status
      - git_diff
      - git_log

Read-only operations that do not modify state are generally safe. These rules allow them at Tier 0 without further evaluation, keeping latency low for the most common operations.

Allow Everything Except External Communication (Permissive Mode)

yaml
deny:
  - name: block_critical_system_paths
    action_types:
      - read_file
      - write_file
      - delete_file
    paths:
      - "~/.ssh/id_*"
      - "~/.aws/credentials"
      - "/etc/shadow"

verify:
  - name: evaluate_external_sends
    action_types:
      - send_message
      - send_email
      - http_request
    tier_override: 1

allow:
  - name: allow_all_file_ops
    action_types:
      - read_file
      - write_file
      - delete_file
      - move_file
      - copy_file
      - create_directory
      - list_directory
      - search_files

  - name: allow_shell
    action_types:
      - execute_command

This permissive policy trusts the LLM for local operations but evaluates anything that communicates externally. Suitable for trusted development environments where speed matters more than maximum security.

Rule Ordering

Within each section (deny, verify, allow), rules are evaluated in the order they appear in the YAML file. First match wins within each section. Across sections, the order is always: deny first, then verify, then allow.

This means you can create specific exceptions before broad rules:

yaml
deny:
  # Specific: block all SSH keys
  - name: block_ssh_keys
    action_types: [read_file]
    paths: ["~/.ssh/**"]

  # Broad: block all dotfiles
  - name: block_dotfiles
    action_types: [read_file, write_file]
    paths: ["~/.*/**"]

allow:
  # But allow reading .gitconfig
  # This will never be reached for ~/.ssh/** because deny runs first!
  - name: allow_gitconfig
    action_types: [read_file]
    paths: ["~/.gitconfig"]

WARNING

An allow rule cannot override a deny rule. If a deny rule matches, the action is blocked immediately and allow rules are never checked. To create an exception within a deny category, restructure your deny rules to be more specific.

Default Policy

Shield ships with three built-in policies:

PolicyFileDescription
Defaultsecurity/shield/default.yamlBalanced security. Blocks sensitive paths, evaluates shell commands and external communication, allows workspace reads.
Strictsecurity/shield/strict.yamlMaximum security. Evaluates all writes at Tier 2, blocks destructive operations and browser interactions.
Permissivesecurity/shield/permissive.yamlMinimal friction. Allows most operations, only evaluates external communication.

See Policy Syntax for the full reference and Configuration for how to select a policy.

Loading Policies

The policy engine loads the YAML file once at initialization and compiles all glob patterns. Patterns that fail to compile are silently skipped (this is logged when a logger is configured).

go
engine, err := tier0.NewPolicyEngine("security/shield/default.yaml")
if err != nil {
    // File not found, YAML parse error, etc.
    // In fail-closed mode, Shield blocks everything when policy loading fails.
    log.Fatal(err)
}

result := engine.Evaluate(&types.ActionRequest{
    Type:    "read_file",
    Payload: map[string]any{"path": "/home/user/.ssh/id_rsa"},
})
// result.Decision == Deny
// result.Reason == "block_sensitive_system_paths"

Next Steps