Policy-as-code for AI agent permissions.
Define bounded permissions for AI agents in YAML. Enforce at runtime. Prevent scope creep.
AI agents need bounded permissions. Current solutions are either:
- Sandboxes (E2B, Daytona) — heavy, require infrastructure
- Custom code — rebuilt per project, error-prone
- Nothing — agents run unrestricted
agent-guard is a lightweight CLI + library that lets you declare exactly what an agent can do, then enforce it on every tool call.
pip install agent-guardagent-guard init my-agent --output .agent-guard.yaml# .agent-guard.yaml - Minimal working example
name: minimal-agent
default_action: deny
rules:
- action: allow
tool: fs.read
resource: "./**/*"This policy:
- Denies everything by default
- Allows reading files in the current directory
- Blocks all other operations
# Test allowed operation
agent-guard check .agent-guard.yaml --tool fs.read --resource ./src/main.py
# ✅ True — allowed
# risk: low
# Test denied operation
agent-guard check .agent-guard.yaml --tool shell --resource "rm -rf /"
# ❌ False — denied by default_action
# risk: mediumPre-made templates for common scenarios. Copy, customize, and use.
The same templates are checked in under examples/policies/.
Start with one and audit it before adapting it to your agent:
cp examples/policies/read-only.yaml .agent-guard.yaml
agent-guard audit .agent-guard.yamlPerfect for code review agents that should only read files and run read-only git commands.
# code-reviewer.yaml
name: code-reviewer
default_action: deny
max_tool_calls: 100
# Block all destructive operations
blocked_tools:
- destructive_*
- admin_*
- shell:rm*
- shell:del*
rules:
# Read project files
- action: allow
tool: fs.read
resource: "./**/*"
description: "Read any project file"
# Read-only git commands
- action: allow
tool: shell
resource: "git log *"
description: "View git history"
- action: allow
tool: shell
resource: "git show *"
description: "Show git commits"
- action: allow
tool: shell
resource: "git diff *"
description: "View git diffs"
- action: allow
tool: shell
resource: "git status"
description: "Check git status"
# Block write operations
- action: deny
tool: shell
resource: "git push *"
description: "Prevent pushing changes"
- action: deny
tool: fs.write
resource: "./**/*"
description: "Prevent file modifications"Use case: CI/CD code review agents that analyze but don't modify code.
Allows web browsing but restricts to specific domains.
# research-agent.yaml
name: research-agent
default_action: deny
# Domain allowlist
allowed_domains:
- github.com
- stackoverflow.com
- docs.python.org
- arxiv.org
# Block all other domains
blocked_domains:
- "*"
rules:
# Allow reading project files
- action: allow
tool: fs.read
resource: "./**/*"
# Allow web requests to approved domains
- action: allow
tool: http.get
resource: "https://github.com/**"
description: "Browse GitHub"
- action: allow
tool: http.get
resource: "https://stackoverflow.com/**"
description: "Search StackOverflow"
- action: allow
tool: http.get
resource: "https://docs.python.org/**"
description: "Read Python docs"
- action: allow
tool: http.get
resource: "https://arxiv.org/**"
description: "Browse research papers"
# Block all other web requests
- action: deny
tool: http.*
resource: "*"
description: "Block unapproved domains"
# Allow local file writes (for saving research)
- action: allow
tool: fs.write
resource: "./research/**"
description: "Save research notes"Use case: Research agents that browse the web but stay within approved domains.
Restricts agent to a specific directory tree.
# sandbox.yaml
name: filesystem-sandbox
default_action: deny
rules:
# Allow operations within sandbox directory only
- action: allow
tool: fs.read
resource: "./sandbox/**"
description: "Read within sandbox"
- action: allow
tool: fs.write
resource: "./sandbox/**"
description: "Write within sandbox"
- action: allow
tool: fs.delete
resource: "./sandbox/**"
description: "Delete within sandbox"
# Block all operations outside sandbox
- action: deny
tool: fs.*
resource: "../**"
description: "Prevent parent directory access"
- action: deny
tool: fs.*
resource: "/**"
description: "Prevent system directory access"
# Allow shell commands only within sandbox
- action: allow
tool: shell
resource: "cd ./sandbox *"
description: "Change to sandbox directory"
- action: deny
tool: shell
resource: "cd .. *"
description: "Prevent escaping sandbox"Use case: Untrusted agents that should only operate within a isolated directory.
For deployment agents with strict operational boundaries.
# deploy-agent.yaml
name: ci-deploy-agent
default_action: deny
max_tool_calls: 30
rules:
# Allow reading deployment configs
- action: allow
tool: fs.read
resource: "./deploy/**"
description: "Read deployment configs"
- action: allow
tool: fs.read
resource: "./.github/**"
description: "Read CI/CD configs"
# Allow specific deployment commands
- action: allow
tool: shell
resource: "kubectl apply *"
description: "Apply Kubernetes manifests"
- action: allow
tool: shell
resource: "docker build *"
description: "Build Docker images"
- action: allow
tool: shell
resource: "docker push *"
description: "Push Docker images"
# Block dangerous operations
- action: deny
tool: shell
resource: "kubectl delete *"
description: "Prevent resource deletion"
- action: deny
tool: shell
resource: "docker rm *"
description: "Prevent container removal"
- action: deny
tool: shell
resource: "rm *"
description: "Prevent file deletion"
# Block production access unless explicitly allowed
- action: deny
tool: shell
resource: "*--namespace=production*"
description: "Block production namespace"Use case: Automated deployment agents with strict operational boundaries.
Different policies for different agent roles.
# multi-agent.yaml
name: multi-agent-system
default_action: deny
# Agent-specific rules
agents:
# Research agent - can browse web
research:
rules:
- action: allow
tool: http.get
resource: "*"
- action: allow
tool: fs.read
resource: "./**/*"
- action: deny
tool: fs.write
resource: "./**/*"
# Coding agent - can write code but not browse web
coder:
rules:
- action: deny
tool: http.*
resource: "*"
- action: allow
tool: fs.read
resource: "./src/**"
- action: allow
tool: fs.write
resource: "./src/**"
- action: allow
tool: shell
resource: "pytest *"
# Reviewer agent - read-only
reviewer:
rules:
- action: allow
tool: fs.read
resource: "./**/*"
- action: allow
tool: shell
resource: "git log *"
- action: deny
tool: fs.write
resource: "./**/*"
- action: deny
tool: shell
resource: "git push *"Use case: Multi-agent systems where each agent has different permission levels.
┌─────────────────────────────────────────────────────────────┐
│ Tool Call Request │
│ (e.g., fs.read ./src/main.py) │
└────────────────────┬────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Load Policy (.agent-guard.yaml) │
│ │
│ name: my-agent │
│ default_action: deny │
│ rules: │
│ - action: allow │
│ tool: fs.read │
│ resource: "./**/*" │
└────────────────────┬────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Evaluate Rules (First Match Wins) │
│ │
│ Rule 1: fs.read ./**/* → MATCH ✅ │
│ Rule 2: shell rm * → no match │
│ Rule 3: default_action: deny → fallback │
└────────────────────┬────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Generate Verdict │
│ │
│ allowed: true │
│ reason: "Allowed by rule: Read project files" │
│ risk: low │
│ matched_rule: 1 │
└────────────────────┬────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Return Result │
│ │
│ Exit code: 0 (allowed) or 1 (denied) │
│ Output: JSON or human-readable table │
└─────────────────────────────────────────────────────────────┘
Flow:
- Agent makes a tool call request
agent-guardloads the policy file- Evaluates rules in order (first match wins)
- Returns verdict (allowed/denied) with reason
- Agent proceeds or blocks based on verdict
A: The default_action determines the outcome. If default_action: deny, unmatched operations are blocked. If default_action: allow, they're permitted. We recommend deny for security.
A: Yes! Use * to match multiple tools. For example:
fs.*matchesfs.read,fs.write,fs.deleteshell:git*matchesshell:git log,shell:git push, etc.http.*matcheshttp.get,http.post, etc.
A: Resource patterns support:
- Glob patterns:
./**/*matches all files recursively - Prefix matching:
git *matches any string starting with "git " - Exact matching:
./src/main.pymatches only that file - Regex: Use
regex:prefix for complex patterns
Examples:
resource: "./**/*" # All files in current directory
resource: "./src/*.py" # Python files in src/
resource: "git *" # Any git command
resource: "https://*.com" # Any .com domainA: Yes! Rules are evaluated in order, first match wins. Put specific rules first:
rules:
# Allow safe git commands
- action: allow
tool: shell
resource: "git log *"
- action: allow
tool: shell
resource: "git status"
# Block dangerous git commands
- action: deny
tool: shell
resource: "git push *"
- action: deny
tool: shell
resource: "git reset --hard *"A: Use the check command:
agent-guard check .agent-guard.yaml --tool fs.read --resource ./src/main.pyThis tells you if the operation would be allowed, which rule matched, and the risk level.
A: Yes! Use max_tool_calls:
name: limited-agent
max_tool_calls: 50 # Agent can make at most 50 tool calls
rules:
- action: allow
tool: fs.read
resource: "./**/*"After 50 calls, all subsequent calls are denied.
A: Each verdict includes a risk level:
- LOW: Read-only operations (fs.read, git log)
- MEDIUM: Write operations (fs.write, shell commands)
- HIGH: Destructive operations (fs.delete, rm)
- CRITICAL: System-level operations (sudo, system paths)
Risk is auto-calculated based on the tool and resource.
A: Absolutely! Exit codes are CI-friendly:
0= allowed1= denied
Example GitHub Actions step:
- name: Check agent permissions
run: |
agent-guard check .agent-guard.yaml \
--tool ${{ steps.agent.outputs.tool }} \
--resource ${{ steps.agent.outputs.resource }}A: Use the audit command:
agent-guard audit .agent-guard.yaml --format tableThis shows all effective rules, what they allow/deny, and any gaps or overlaps.
A: Yes! Create separate policy files:
# Research agent
agent-guard check research-agent.yaml --tool http.get --resource "https://example.com"
# Coding agent
agent-guard check coding-agent.yaml --tool fs.write --resource "./src/main.py"Or use the multi-agent template (see Policy Templates section).
- YAML policies — version-controlled, reviewable
- Rule ordering — first match wins (like firewall rules)
- Tool wildcards —
fs.*matchesfs.read,fs.write - Resource patterns — glob + regex support
- Domain controls — allowlist/blocklist for network tools
- Execution limits — max tool calls per session
- Risk scoring — LOW/MEDIUM/HIGH/CRITICAL per verdict
- Multiple outputs — JSON for automation, table for humans
- Exit codes — 0 = allowed, 1 = denied (CI-friendly)
| Command | Purpose |
|---|---|
check |
Test a tool call against policy |
explain |
Show which rule matched and why |
audit |
Review effective ruleset |
init |
Generate starter policy |
Import classes directly from the package:
from agent_guard import Guard, Policy, Rule, ToolCall, Action, RiskLevel
# 1. Load or construct a Policy
policy = Policy.from_file(".agent-guard.yaml")
# Alternatively, from a YAML string:
# policy = Policy.from_yaml(yaml_string)
# Or construct programmatically:
# policy = Policy(
# name="custom-agent",
# description="Custom security policy",
# default_action=Action.DENY,
# rules=[Rule(action=Action.ALLOW, tool="fs.read", resource="./**/*")],
# max_tool_calls=50,
# )
# 2. Instantiate Guard
guard = Guard(policy)
# 3. Check / evaluate tool calls
call = ToolCall(tool="fs.read", resource="./src/main.py", arguments={"mode": "r"})
verdict = guard.check(call)
if verdict.allowed:
print(f"Allowed: {verdict.reason} (Risk: {verdict.risk.value})")
else:
print(f"Blocked: {verdict.reason} (Risk: {verdict.risk.value})")Policy:Policy.from_file(path: str | Path) -> Policy: Load policy from YAML file.Policy.from_yaml(text: str) -> Policy: Parse policy from YAML string.- Attributes:
name,description,default_action(Action),rules(list[Rule]),max_tool_calls(int | None),max_executions(int | None),blocked_tools(list[str]),allowed_domains(list[str]),blocked_domains(list[str]).
Guard:Guard(policy: Policy): Initializes runtime guard with execution and call counters.guard.check(call: ToolCall) -> Verdict: Evaluates tool call against limits, blocked tools, domain rules, and ordered policy rules.
ToolCall:ToolCall(tool: str, resource: str = "", arguments: dict[str, Any] = ...): Represents an agent action.
Verdict:allowed: bool: Whether the action is permitted.rule: Rule | None: Matched rule if applicable, or None if fell through to default/limits.reason: str: Human-readable justification.risk: RiskLevel: Risk category (RiskLevel.LOW,RiskLevel.MEDIUM,RiskLevel.HIGH,RiskLevel.CRITICAL).
Rule:Rule(action: Action, resource: str, tool: str, description: str = ""): Rule definition with glob/regex matching.
Agent Guard policies are written in standard YAML. Below are all available fields:
| Field | Type | Default | Description |
|---|---|---|---|
name |
string |
(required) | Identifier for the policy. |
description |
string |
"" |
Description of the agent's role and boundaries. |
default_action |
string (allow | deny) |
"deny" |
Fallback action when no rules match. |
max_tool_calls |
integer |
null |
Maximum tool calls allowed per session before tripping a block. |
max_executions |
integer |
null |
Maximum policy execution count. |
blocked_tools |
list[string] |
[] |
Glob patterns for tools that are unconditionally blocked (e.g. destructive_*). |
allowed_domains |
list[string] |
[] |
Glob patterns for permitted domains on network tools (browser, http, fetch, request). If specified, any non-matching domain is blocked. |
blocked_domains |
list[string] |
[] |
Glob patterns for domains that are explicitly blocked. |
rules |
list[object] |
[] |
Ordered list of evaluation rules evaluated top-to-bottom. First match wins. |
| Field | Type | Default | Description |
|---|---|---|---|
action |
string (allow | deny) |
(required) | Verdict returned when this rule matches. |
tool |
string |
"*" |
Tool name pattern. Supports wildcards (e.g., fs.*, shell). |
resource |
string |
"*" |
Target resource path, glob (./**/*.py), command prefix (git *), or regex. |
description |
string |
"" |
Human-readable explanation shown in audit and check logs. |
Enables reading repository files and running read-only git queries, while preventing any file writes or remote repository pushes:
name: code-reviewer
description: "Restricted policy for autonomous code review"
default_action: deny
max_tool_calls: 50
blocked_tools:
- destructive_*
- admin_*
rules:
# Allow reading project code
- action: allow
tool: fs.read
resource: "./**/*"
description: "Read project files"
# Allow read-only git commands
- action: allow
tool: shell
resource: "git diff *"
description: "Inspect code diffs"
- action: allow
tool: shell
resource: "git log *"
description: "Inspect commit history"
- action: allow
tool: shell
resource: "git status"
description: "Check working tree status"
# Deny git mutations or pushes
- action: deny
tool: shell
resource: "git push *"
description: "Prevent pushing commits"A zero-mutation policy suitable for security scanning or static code analysis:
name: read-only-audit
description: "Strict read-only policy for static analysis and inspection"
default_action: deny
max_tool_calls: 100
blocked_tools:
- shell
- fs.write
- fs.delete
- exec_*
rules:
# Allow directory listing and inspection
- action: allow
tool: fs.read
resource: "./**/*"
description: "Read repository contents"
# Allow querying internal security docs or allowlisted APIs
- action: allow
tool: http
resource: "https://api.github.com/*"
description: "Query GitHub API"Allows executing deployment tools only against specific target environments with strict domain restrictions:
name: deploy-gate
description: "Production and staging deployment gate policy"
default_action: deny
max_tool_calls: 20
allowed_domains:
- "*.internal.company.com"
- "api.github.com"
blocked_domains:
- "*.public-cloud-sandbox.net"
rules:
# Allow running staging deployments
- action: allow
tool: deploy
resource: "staging-*"
description: "Allow automated staging deployments"
# Deny direct production deployment tool calls
- action: deny
tool: deploy
resource: "production-*"
description: "Block unapproved production deployment"
# Allow notifications/status pings
- action: allow
tool: http
resource: "https://*.internal.company.com/webhook/*"
description: "Send status notifications"Run agent-guard audit against your policy YAML file:
agent-guard audit .agent-guard.yaml --format tableYou can also specify --format json or --format yaml for automated validation and CI pipelines.
Use agent-guard check or agent-guard explain with test tool calls:
# Test whether a file write is prevented:
agent-guard check .agent-guard.yaml --tool fs.write --resource /etc/shadow
# Inspect rule resolution and rationale:
agent-guard explain .agent-guard.yaml --tool shell --resource "rm -rf /"- Tool names: Glob patterns matching tool names (e.g.
fs.*matchesfs.readandfs.write). - Resources: Supports path globs (
./**/*.py), prefixes (git *), and regex syntax for advanced resource constraints.
Rules are evaluated sequentially from top to bottom. The first matching rule determines the verdict (allow or deny). If no rule matches, the policy falls back to default_action.
- CI/CD pipelines — restrict agent to read-only operations
- Code review agents — allow
git logbut blockgit push - Browser agents — restrict to specific domains
- Multi-agent systems — different policies per agent
- Compliance — audit trail of all enforcement decisions
| Tool | Self-hosted | Declarative | Lightweight | Domain-aware |
|---|---|---|---|---|
| agent-guard | ✅ | ✅ | ✅ | ✅ |
| E2B | ❌ | ❌ | ❌ | ❌ |
| Daytona | ✅ | ❌ | ❌ | ❌ |
| OpenAI sandbox | ❌ | ❌ | ❌ | ❌ |
| Custom code | ✅ | ❌ | ✅ | ❌ |
MIT © Yunare Maia