Policy Engine
The Policy Engine is the core of EnforceCore. It evaluates every tool call against a set of declarative rules to decide whether to allow, block, or modify the execution. Policies are Pydantic-validated, composable via inheritance, and separate security rules from application code.
Policy Structure
Policies are defined in YAML files. A minimal policy:
name: "my-policy"
version: "1.0"
on_violation: "block" # "block" or "log"
rules:
allowed_tools:
- search_web
- calculator
denied_tools:
- execute_shell
- delete_fileA fully-featured policy:
name: "production"
version: "1.0"
extends: "base-org-policy.yaml" # Inherit from a base policy
on_violation: "block"
rules:
# Tool allow/deny lists
allowed_tools: # null = allow all (except denied)
- search_web
- calculator
- read_file
denied_tools: # Always denied (takes priority over allowed)
- execute_shell
- delete_file
- send_email
# PII redaction
pii_redaction:
enabled: true
categories:
- email
- phone
- ssn
- credit_card
- ip_address
strategy: "placeholder" # placeholder | mask | hash | remove
# Resource limits per call
resource_limits:
max_call_duration_seconds: 30
max_memory_mb: 256
max_cost_usd: 5.00
# Network domain enforcement
network:
enabled: true
allowed_domains:
- "api.openai.com"
- "api.anthropic.com"
- "*.wikipedia.org" # Wildcard support (fnmatch)
denied_domains:
- "*.malicious.xyz"
deny_all_other: true # Block domains not in allowed list
# Content rules (pattern-based blocking)
content_rules:
enabled: true
block_patterns:
- name: "shell_injection"
pattern: "[;|&]|../|rm\\s+-rf"
action: "block"
- name: "sql_injection"
pattern: "('[\\s]*OR\\s+1|UNION\\s+SELECT|DROP\\s+TABLE)"
action: "block"
# Rate limiting (sliding window)
rate_limits:
enabled: true
per_tool:
search_web:
max_calls: 10
window_seconds: 60
llm_call:
max_calls: 50
window_seconds: 60
global:
max_calls: 500
window_seconds: 60
# Output constraints
max_output_size_bytes: 524288 # 512KB
redact_output: true # Redact PII from return valuesTop-Level Fields
| Field | Type | Default | Description |
|---|---|---|---|
name |
str |
required | Policy name (non-empty). |
version |
str |
"1.0" |
Semantic version. |
extends |
str | null |
null |
Path to a parent policy for inheritance. |
on_violation |
str |
"block" |
"block" raises exceptions; "log" records but allows execution. |
rules |
object |
{} |
The rule configuration (see below). |
Rules Reference
allowed_tools / denied_tools
Control which tools/functions can be called.
allowed_tools: null— allow all tools (except those indenied_tools)allowed_tools: [search_web, calculator]— only these tools are alloweddenied_toolsalways takes priority overallowed_tools
rules:
allowed_tools: null # Allow everything except denied
denied_tools:
- execute_shell
- send_emailpii_redaction
Configure PII detection and redaction on inputs and outputs.
| Field | Type | Default | Description |
|---|---|---|---|
enabled |
bool |
false |
Enable PII scanning. |
categories |
list[str] |
[email, phone, ssn, credit_card, ip_address] |
PII types to detect. |
strategy |
str |
"placeholder" |
placeholder, mask, hash, or remove. |
resource_limits
Enforce hard limits per function call.
| Field | Type | Default | Description |
|---|---|---|---|
max_call_duration_seconds |
float | null |
null |
Kill calls exceeding this time. |
max_memory_mb |
int | null |
null |
Kill calls exceeding this RSS memory. |
max_cost_usd |
float | null |
null |
Session cost budget in USD. |
network
Control outbound network access by domain.
| Field | Type | Default | Description |
|---|---|---|---|
enabled |
bool |
false |
Enable domain checking. |
allowed_domains |
list[str] |
[] |
Permitted domains (supports *.example.com wildcards). |
denied_domains |
list[str] |
[] |
Blocked domains. |
deny_all_other |
bool |
true |
If true, only allowed_domains are reachable. |
content_rules
Block requests containing dangerous patterns (injection attacks, prompt manipulation).
| Field | Type | Default | Description |
|---|---|---|---|
enabled |
bool |
false |
Enable content scanning. |
block_patterns |
list[object] |
[] |
Patterns to block: {name, pattern, action}. |
rate_limits
Sliding-window rate limiting per tool and globally.
| Field | Type | Default | Description |
|---|---|---|---|
enabled |
bool |
false |
Enable rate limiting. |
per_tool |
dict |
{} |
Per-tool limits: {tool_name: {max_calls, window_seconds}}. |
global |
object | null |
null |
Global limit: {max_calls, window_seconds}. |
Policy Inheritance
Policies can extend a parent policy using extends. This enables organization-level base policies overridden by project-specific rules.
# base.yaml — organization default
name: "org-base"
rules:
denied_tools: [execute_shell, delete_file]
pii_redaction:
enabled: true
categories: [email, phone, ssn]# project.yaml — inherits and overrides
name: "project-alpha"
extends: "base.yaml"
rules:
allowed_tools: [search_web, calculator]
resource_limits:
max_call_duration_seconds: 30Merge Semantics
When merging base + override:
| Field | Merge Behavior |
|---|---|
name, version, on_violation |
Override wins. |
allowed_tools |
Override wins if set; otherwise base. |
denied_tools |
Union (both lists combined, deduplicated). |
pii_redaction |
Override wins if enabled: true. |
resource_limits |
Override wins for each non-null field. |
network.denied_domains |
Union. |
content_rules.block_patterns |
Union (by name). |
rate_limits.per_tool |
Override wins per tool. |
You can also merge programmatically:
from enforcecore import Policy
base = Policy.from_file("base.yaml")
override = Policy.from_file("project.yaml")
merged = Policy.merge(base, override)Policy Validation
Validate a policy file without loading it (useful in CI pipelines):
from enforcecore import Policy
errors = Policy.validate_file("policy.yaml")
if errors:
for error in errors:
print(f"ERROR: {error}")
else:
print("Policy is valid")Or via CLI:
enforcecore policy validate policy.yamlDry Run
Preview what a policy would do for a given tool without executing anything:
from enforcecore import Policy
policy = Policy.from_file("policy.yaml")
result = policy.dry_run("execute_shell")
print(result)
# {"decision": "blocked", "reason": "tool in denied_tools", ...}Or via CLI:
enforcecore policy dry-run policy.yaml --tool execute_shellBest Practices
- Start with
on_violation: "log": Run in production to gather data without breaking functionality. Switch to"block"once confident. - Use specific tool lists: Prefer explicit
allowed_toolsover allowing everything. - Layer your policies: Keep common rules (PII, denied tools) in a base policy and
extendsit per project. - Validate in CI: Add
enforcecore policy validateto your CI pipeline to catch schema errors. - Use content rules for injection protection: Add block patterns for shell injection, SQL injection, and path traversal.
- Audit regularly: Use the Auditor to review policy decisions and tune rules.
See Also
- Enforcer API — How policies are loaded and applied.
- Architecture — How the policy engine fits in the system.
- Defense in Depth — Layering policies with OS-level security.