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:
- Deny rules -- checked first. If any deny rule matches, the action is immediately BLOCKED. No further evaluation occurs.
- 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. - Allow rules -- checked last. If an allow rule matches, the action is ALLOWED (subject to
min_tieroverrides 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 1Policy File Structure
A policy file is a YAML document with three top-level keys:
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
| Field | Type | Required | Description |
|---|---|---|---|
name | string | yes | Unique identifier for the rule. Appears in verdicts and audit logs. |
action_types | string[] | no | List of action type strings to match. If omitted, matches all action types. |
paths | string[] | no | List of glob patterns to match against file paths in the action payload. If omitted, matches regardless of path. |
tier_override | int | no | For 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 Type | Description |
|---|---|
read_file | Read a file |
write_file | Write/create a file |
delete_file | Delete a file |
copy_file | Copy a file |
move_file | Move/rename a file |
list_directory | List directory contents |
create_directory | Create a directory |
delete_directory | Delete a directory |
execute_command | Execute a shell command |
git_commit | Git commit |
git_push | Git push |
send_email | Send an email |
send_message | Send a chat message |
http_request | Make an HTTP request |
memory_write | Write to memory |
memory_search | Search memory |
browser_navigate | Navigate browser |
canvas_create | Create 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.
| Pattern | Matches | Does 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.md | SOUL.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_filewithpath: ~/.ssh/id_rsacopy_filewithsource: ~/.ssh/id_rsaordestination: ~/.ssh/id_rsamove_filewithsource: ~/.ssh/configordestination: ~/.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
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
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
verify:
- name: evaluate_shell_commands
action_types:
- execute_command
tier_override: 1Shell 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
verify:
- name: evaluate_soul_modification
action_types:
- write_file
- copy_file
- move_file
paths:
- "**/SOUL.md"
- "**/IDENTITY.md"
tier_override: 2Modifying 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
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_logRead-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)
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_commandThis 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:
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:
| Policy | File | Description |
|---|---|---|
| Default | security/shield/default.yaml | Balanced security. Blocks sensitive paths, evaluates shell commands and external communication, allows workspace reads. |
| Strict | security/shield/strict.yaml | Maximum security. Evaluates all writes at Tier 2, blocks destructive operations and browser interactions. |
| Permissive | security/shield/permissive.yaml | Minimal 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).
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
- Tier 1 -- Classifier -- what happens when Tier 0 escalates
- Policy Syntax -- full reference for all three shipped policies
- Configuration -- how to configure the policy file path