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_file

A 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 values

Top-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 in denied_tools)
  • allowed_tools: [search_web, calculator] — only these tools are allowed
  • denied_tools always takes priority over allowed_tools
rules:
  allowed_tools: null              # Allow everything except denied
  denied_tools:
    - execute_shell
    - send_email

pii_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: 30

Merge 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.yaml

Dry 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_shell

Best Practices

  1. Start with on_violation: "log": Run in production to gather data without breaking functionality. Switch to "block" once confident.
  2. Use specific tool lists: Prefer explicit allowed_tools over allowing everything.
  3. Layer your policies: Keep common rules (PII, denied tools) in a base policy and extends it per project.
  4. Validate in CI: Add enforcecore policy validate to your CI pipeline to catch schema errors.
  5. Use content rules for injection protection: Add block patterns for shell injection, SQL injection, and path traversal.
  6. Audit regularly: Use the Auditor to review policy decisions and tune rules.

See Also