Declarative firewall for AI agent tool calls
Project description
🛡️ PolicyShield
Declarative firewall for AI agent tool calls.
Write rules in YAML → PolicyShield enforces them at runtime → get a full audit trail.
LLM calls web_fetch(url="...?email=john@corp.com")
│
▼
PolicyShield intercepts
│
├─ PII detected → REDACT → tool runs with masked args
├─ Destructive cmd → BLOCK → tool never executes
└─ Sensitive action → APPROVE → human reviews first
Installation
pip install policyshield
Or from source:
git clone https://github.com/mishabar410/PolicyShield.git
cd PolicyShield
pip install -e ".[dev]"
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 import ShieldEngine
engine = ShieldEngine("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:
policyshield init --preset security --no-interactive
Using with Nanobot
PolicyShield integrates with nanobot to enforce policies on all tool calls your agent makes.
Step 1. Install PolicyShield alongside nanobot
# In your nanobot project:
pip install policyshield
Step 2. Create rules for your agent
Create policies/rules.yaml in your project root:
shield_name: my-nanobot
version: 1
rules:
# Block dangerous shell commands
- id: block-rm-rf
when:
tool: exec
args_match:
command: { contains: "rm -rf" }
then: block
message: "Destructive shell commands are not allowed."
# Redact PII from any outgoing messages
- id: redact-pii-messages
when:
tool: send_message
then: redact
# Block all file deletion
- id: block-delete
when:
tool: delete_file
then: block
message: "File deletion is disabled."
Step 3. Run nanobot through PolicyShield
The simplest way — just prefix your usual command:
policyshield nanobot --rules policies/rules.yaml agent -m "Hello!"
policyshield nanobot --rules policies/rules.yaml gateway
Or if you create AgentLoop in your own Python code:
from nanobot.agent.loop import AgentLoop
from policyshield.integrations.nanobot import shield_agent_loop
loop = AgentLoop(bus=bus, provider=provider, workspace=workspace)
shield_agent_loop(loop, rules_path="policies/rules.yaml") # ← one line
That's it. Every tool call your agent makes will now pass through PolicyShield. Blocked tools return an error message to the LLM, which replans automatically.
What happens under the hood
shield_agent_loop() monkey-patches your existing loop instance (no nanobot source changes needed):
- Wraps the ToolRegistry — every
execute()call is checked against your rules - Filters blocked tools from LLM context — the LLM never sees tools it can't use
- Injects constraints into the system prompt — the LLM knows what's forbidden
- Scans tool results for PII — post-call audit and tainting
- Tracks sessions — rate limits work per-conversation
Optional: standalone mode (no AgentLoop)
You can also use PolicyShield with nanobot's ToolRegistry directly, without AgentLoop:
from policyshield.integrations.nanobot.installer import install_shield
# Create a shielded registry
registry = install_shield(rules_path="policies/rules.yaml")
# Register your tools
registry.register_func("echo", lambda message="": f"Echo: {message}")
registry.register_func("delete_file", lambda path="": f"Deleted {path}")
# This works:
result = await registry.execute("echo", {"message": "hello"})
# → "Echo: hello"
# This is blocked:
result = await registry.execute("delete_file", {"path": "/etc/passwd"})
# → "🛡️ BLOCKED: File deletion is disabled."
Configuration options
shield_agent_loop(
loop,
rules_path="policies/rules.yaml", # Required. Path to YAML rules
mode="ENFORCE", # ENFORCE (default) | AUDIT (log only) | DISABLED
fail_open=True, # True (default): shield errors don't block tools
)
See the full nanobot integration guide for approval flows, custom PII patterns, rate limiting, and more.
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
# 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
| 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 |
| Async Engine | Full async/await support for FastAPI, aiohttp, async agents |
| Approval Flow | InMemory, CLI, Telegram, and Webhook backends with caching strategies |
| Rate Limiting | Sliding-window per tool/session, configurable in YAML |
| Hot Reload | File-watcher auto-reloads rules on change |
| Input Sanitizer | Normalize args, block prompt injection patterns |
| OpenTelemetry | OTLP export to Jaeger/Grafana (spans + metrics) |
| Trace & Audit | JSONL log, search, stats, violations, CSV/HTML export |
| 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 and PII labels + Grafana preset |
| Rule Testing | YAML test cases for policies (policyshield test) |
| Rule Linter | Static analysis: duplicates, broad patterns, missing messages, conflicts |
Other Integrations
LangChain
from policyshield.integrations.langchain import PolicyShieldTool, shield_all_tools
safe_tool = PolicyShieldTool(wrapped_tool=my_tool, engine=engine)
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
policyshield validate ./policies/ # Validate rules
policyshield lint ./policies/rules.yaml # Static analysis (6 checks)
policyshield test ./policies/ # Run YAML test cases
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
# Launch the live web dashboard
policyshield trace dashboard --port 8000 --prometheus
# Run nanobot with PolicyShield enforcement
policyshield nanobot --rules rules.yaml agent -m "Hello!"
policyshield nanobot --rules rules.yaml gateway
# Initialize a new project
policyshield init --preset security --no-interactive
Docker
# Validate rules
docker compose run policyshield validate policies/
# Lint rules
docker compose run lint
# Run tests
docker compose run test
Examples
| Example | Description |
|---|---|
nanobot_shield_example.py |
Nanobot standalone — run this to see PolicyShield in action |
nanobot_shield_agentloop.py |
AgentLoop configuration reference |
nanobot_rules.yaml |
Example policy rules for nanobot |
langchain_demo.py |
LangChain tool wrapping |
async_demo.py |
Async engine usage |
policies/ |
Production-ready rule sets (security, compliance, full) |
Development
git clone https://github.com/mishabar410/PolicyShield.git
cd PolicyShield
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev,langchain]"
pytest tests/ -v # 690+ tests
ruff check policyshield/ tests/ # Lint
ruff format --check policyshield/ tests/ # Format check
📖 Documentation: mishabar410.github.io/PolicyShield
Roadmap
| Version | Status |
|---|---|
| v0.1 | ✅ Core: YAML DSL, verdicts, PII, trace, CLI |
| v0.2 | ✅ Linter, hot reload, rate limiter, approval flow, LangChain |
| v0.3 | ✅ Async engine, CrewAI, OTel, webhooks, rule testing, policy diff |
| v0.4 | ✅ Nanobot: monkey-patch, CLI wrapper, session propagation, PII scan |
| v0.5 | ✅ DX: PyPI publish, docs site, GitHub Action, Docker, CLI init |
| v0.6 | ✅ Observability: trace search, cost estimator, alerts, dashboard, Grafana |
| v1.0 | 📋 Stable API, performance benchmarks, multi-tenant |
See ROADMAP.md for the full roadmap including v0.7–v1.0 and future ideas.
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.6.0.tar.gz.
File metadata
- Download URL: policyshield-0.6.0.tar.gz
- Upload date:
- Size: 92.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c7a2df2b62dcf94ba7af116597a078d1c35d2c395e85c0a46acc06fa45a0f91a
|
|
| MD5 |
ed9a1775ffbb8f67e072d8c7f5f4625e
|
|
| BLAKE2b-256 |
15766919625dc1ea28d234558e5ebe02a7c4c22846195f96d5f7d2220e3c5890
|
Provenance
The following attestation bundles were made for policyshield-0.6.0.tar.gz:
Publisher:
release.yml on mishabar410/PolicyShield
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
policyshield-0.6.0.tar.gz -
Subject digest:
c7a2df2b62dcf94ba7af116597a078d1c35d2c395e85c0a46acc06fa45a0f91a - Sigstore transparency entry: 945497721
- Sigstore integration time:
-
Permalink:
mishabar410/PolicyShield@5056b090e518e62cf0fe55ff8cbd1579ddd1c0fe -
Branch / Tag:
refs/tags/v0.6.0 - Owner: https://github.com/mishabar410
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@5056b090e518e62cf0fe55ff8cbd1579ddd1c0fe -
Trigger Event:
push
-
Statement type:
File details
Details for the file policyshield-0.6.0-py3-none-any.whl.
File metadata
- Download URL: policyshield-0.6.0-py3-none-any.whl
- Upload date:
- Size: 99.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
822fef5d1f2451679f093a3e8b221f07a4d9bc1d69cc1806a4fb41a99d25dfce
|
|
| MD5 |
4ffc31d8d9465765e82e7e8752fcbf8b
|
|
| BLAKE2b-256 |
973460f8eabd1d85e3e8ac25f12e93b86d0ff3e0d180a2d2e132b0b008663cbe
|
Provenance
The following attestation bundles were made for policyshield-0.6.0-py3-none-any.whl:
Publisher:
release.yml on mishabar410/PolicyShield
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
policyshield-0.6.0-py3-none-any.whl -
Subject digest:
822fef5d1f2451679f093a3e8b221f07a4d9bc1d69cc1806a4fb41a99d25dfce - Sigstore transparency entry: 945497736
- Sigstore integration time:
-
Permalink:
mishabar410/PolicyShield@5056b090e518e62cf0fe55ff8cbd1579ddd1c0fe -
Branch / Tag:
refs/tags/v0.6.0 - Owner: https://github.com/mishabar410
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@5056b090e518e62cf0fe55ff8cbd1579ddd1c0fe -
Trigger Event:
push
-
Statement type: