Overview
Permission policies let you define trust boundaries for your AI coding sessions. When Claude Code requests permission to use a tool (like writing a file or running a command), CodePiper’s policy engine evaluates the request and can automatically approve, deny, or escalate it.
Every decision is logged in an audit trail.
How It Works
Claude Code hooks ──▶ PermissionRequest event │ ┌──────▼──────┐ │ PolicyEngine │ │ evaluate() │ └──────┬──────┘ │ ┌──────────────┼──────────────┐ │ │ │ ┌────▼───┐ ┌────▼───┐ ┌────▼───┐ │ ALLOW │ │ DENY │ │ ASK │ │ "1"⏎ │ │ "2"⏎ │ │ (wait) │ └────────┘ └────────┘ └────────┘- Claude Code sends a PermissionRequest event via hooks
- The PolicyEngine finds all enabled policies matching the session
- Rules are evaluated in priority order (highest first)
- The first matching rule determines the action
- The decision is stored in the audit log
- For
allow/deny: the daemon sends keystrokes to tmux automatically - For
ask: the request is surfaced to you via the web dashboard or notifications
Policy Structure
A policy contains one or more rules. Each rule specifies conditions and an action:
{ "name": "Allow file reads, deny shell commands", "enabled": true, "priority": 100, "rules": [ { "action": "allow", "tool": "Read", "reason": "Reading files is always safe" }, { "action": "deny", "tool": "Bash", "args": { "command": "rm *" }, "reason": "Prevent destructive shell commands" }, { "action": "ask", "tool": "Bash", "reason": "Review other shell commands manually" } ]}Rule Fields
| Field | Type | Description |
|---|---|---|
action | "allow" | "deny" | "ask" | What to do when this rule matches |
tool | glob pattern | Tool name to match (e.g., "Bash", "Write", "*") |
args | object of patterns | Match against tool arguments |
cwd | glob pattern | Match against working directory |
session | glob pattern | Match against session ID |
reason | string | Explanation logged in audit trail |
Pattern Matching
Tool and argument patterns support glob syntax:
| Pattern | Matches |
|---|---|
"Bash" | Exactly “Bash” |
"*" | Any tool |
"Write" | Exactly “Write” |
"!Bash" | Anything except “Bash” |
["Read", "Glob", "Grep"] | Any of these three tools |
Creating Policies
Via CLI
# Create a simple policycodepiper policy create \ --name "Allow reads" \ --rules '[{"action":"allow","tool":"Read"}]' \ --priority 100
# Create a session-scoped policycodepiper policy create \ --name "Sandbox for session X" \ --session <session-id> \ --rules '[{"action":"deny","tool":"Bash","args":{"command":"rm *"}}]'Via Web Dashboard
Navigate to Policies → Create Policy to build rules with the visual editor.
Via API
curl --unix-socket /tmp/codepiper.sock \ -X POST http://localhost/policies \ -H "Content-Type: application/json" \ -d '{ "name": "Production safety", "enabled": true, "priority": 200, "rules": [ { "action": "deny", "tool": "Bash", "args": { "command": "git push*" } }, { "action": "allow", "tool": "Read" }, { "action": "ask", "tool": "*" } ] }'Policy Sets
Policy sets group related policies for easy assignment to sessions:
# Create a policy setcodepiper policy-set create --name "Strict development"
# Add policies to itcodepiper policy-set add-policy <set-id> <policy-id-1>codepiper policy-set add-policy <set-id> <policy-id-2>You can assign policy sets to sessions at creation time or later. A session can have one active policy set.
Evaluation Order
When a permission request arrives, the policy engine:
- Collects all enabled policies that apply (session-specific + global)
- Sorts by priority, descending (highest priority evaluates first)
- Evaluates each rule in order within each policy
- Returns the first matching rule’s action
- If no rule matches, falls back to the default policy action
The default policy action is configurable via daemon settings:
# Set default to "ask" (prompt user for unmatched requests)codepiper policy default ask
# Set default to "deny" (block unmatched requests)codepiper policy default denyCodex Provider Behavior
Codex doesn’t support native hooks, so policy enforcement works differently:
- The daemon intercepts terminal input before forwarding it to the tmux session
- If a matching rule exists with
askaction, the input is blocked - If the default action is
askwith no explicit match, input is allowed allowanddenyrules work the same as with Claude Code
This means Codex sessions have less granular policy control than Claude Code sessions.
Audit Log
Every policy decision is logged with:
- Session ID
- Tool name and arguments
- Which policy and rule matched
- The decision (allow/deny/ask)
- The reason
- Timestamp
View the audit log:
# All recent decisionscodepiper audit
# Filter by sessioncodepiper audit --session <session-id>
# Limit resultscodepiper audit --limit 20Or in the web dashboard under Policies → Audit Log.
Common Patterns
Allow everything except destructive commands
[ { "action": "deny", "tool": "Bash", "args": { "command": "rm -rf*" } }, { "action": "deny", "tool": "Bash", "args": { "command": "git push --force*" } }, { "action": "allow", "tool": "*" }]Read-only mode
[ { "action": "allow", "tool": ["Read", "Glob", "Grep", "LS"] }, { "action": "deny", "tool": "*", "reason": "Read-only session" }]Review all shell commands, allow everything else
[ { "action": "ask", "tool": "Bash" }, { "action": "allow", "tool": "*" }]What’s Next
- Security Model: Authentication and encryption
- Architecture Overview: System topology
- Sessions & Providers: Provider capabilities