A deterministic governance layer that hooks into before_tool_call to control which tools agents can use, block dangerous commands, enforce write-path restrictions, and audit every decision.
bash
clawhub install policy-engine
Then enable in your openclaw.json:
jsonc
{
"plugins": {
"policy-engine": {
"enabled": true
}
}
}
Minimal restrictive config — limit a sub-agent to read-only tools:
jsonc
{
"plugins": {
"policy-engine": {
"enabled": true,
"allowlists": {
"readonly": ["read", "web_fetch", "web_search", "message"]
},
"routing": {
"research-agent": { "toolProfile": "readonly" }
}
}
}
}
Per-agent profiles controlling which tools are permitted. Assign profiles via routing rules keyed by agent ID.
Built-in patterns block fork bombs, rm -rf, mkfs, disk wipes, and system path writes. Scoped matching checks only relevant params (e.g., command for exec, path for write) — never file content. Add custom patterns per tool.
Canonicalizes file paths via path.resolve() then checks against allowed directory prefixes. Prevents path traversal attacks (e.g., ../../etc/passwd) even via prompt injection.
jsonc
{
"pathAllowlists": {
"write": ["/Users/joe/.openclaw/workspace"],
"edit": ["/Users/joe/.openclaw/workspace"]
}
}
With this config, write to /Users/joe/.openclaw/workspace/foo.txt → allowed. write to /Users/joe/.openclaw/workspace/../../etc/hosts → blocked (resolves to /Users/joe/etc/hosts, outside prefix).
Override with riskTiers map:
jsonc
{ "riskTiers": { "my_custom_tool": "T2" } }
Test policies without blocking. Essential tools (message, gateway, session_status) always pass through to prevent agent deadlock.
jsonc
{ "dryRun": true, "dryRunAllowT0": true }
Counts blocked attempts per session. After maxBlockedRetries (default: 3), further non-essential calls are blocked with a remediation message.
Config changes via gateway config.patch take effect immediately — no restart needed.
If the engine itself throws, the tool call proceeds. Safety over availability of governance.
Set OPENCLAW_POLICY_BYPASS=1 to bypass all checks. Logged as a warning for audit.
FieldTypeDefaultDescriptionenabledbooleantrueGlobal kill-switchdryRunbooleanfalseLog-only mode (no blocking)dryRunAllowT0booleantrueAllow T0 tools in dry-rundryRunEssentialToolsstring[][message, gateway, session_status, sessions_send, sessions_list, tts]Tools that always pass in dry-runmaxBlockedRetriesnumber3Escalation threshold per sessionriskTiersobject{}Tool → "T0"|"T1"|"T2" overridesdenyPatternsobject{}Tool → string[] of blocked argument patternsallowlistsobject{}Profile → string[] of allowed tool namesroutingobject{}AgentId → { model?, toolProfile? }``pathAllowlistsobject{}Tool → string[] of allowed directory prefixes
jsonc
{
"allowlists": {
"researcher": ["read", "web_fetch", "web_search", "message"],
"coder": ["read", "write", "edit", "exec", "message"]
},
"routing": {
"research-bot": { "toolProfile": "researcher" },
"code-bot": { "toolProfile": "coder" }
}
}
jsonc
{
"denyPatterns": {
"exec": ["npm publish", "docker push"],
"write": ["/secrets/", "/credentials/"]
}
}
Enable dry-run to see what would be blocked before enforcing:
jsonc
{ "dryRun": true }
Check logs for [policy-engine] DRYRUN entries, then disable when satisfied.
jsonc
{
"routing": {
"cheap-tasks": { "model": "ollama/qwen2.5:latest" },
"complex-tasks": { "model": "anthropic/claude-opus-4", "toolProfile": "full" }
}
}
The plugin registers a /policy command:
/policy status — show current config and session stats/policy reset — reset escalation countersSee DESIGN.md for detailed design decisions, deadlock analysis, and the three deadlock classes that were discovered and fixed during development.