DocumentationAgent Action Firewall

Policy Engine

Agent Action Firewall uses Open Policy Agent (OPA) as its policy engine, giving you powerful, flexible control over what your agents can do.

How Policy Evaluation Works

  1. Agent submits an action request
  2. OPA evaluates all applicable policies against the action
  3. The most restrictive matching decision is returned
  4. The decision determines if the action proceeds

Policy Structure

Policies are written in Rego and must return a decision:

package aaf.policy

# Default to allow if no rules match
default decision = "allow"

# Deny requests to private networks
decision = "deny" {
  input.action.tool == "http_proxy"
  is_private_network(input.action.params.url)
}

# Require approval for financial operations
decision = "require_approval" {
  input.action.tool == "http_proxy"
  contains(input.action.params.url, "stripe.com")
  input.action.params.body.amount > 10000
}

Input Schema

Your policies receive an input object with:

{
  "action": {
    "id": "action-123",
    "tool": "http",
    "operation": "POST",
    "params": {
      "url": "https://api.stripe.com/v1/charges",
      "headers": { ... },
      "body": { ... }
    },
    "context": {
      "user_id": "user-456",
      "session_id": "session-789",
      "purpose": "Process payment"
    }
  },
  "agent": {
    "id": "agent-001",
    "name": "Payment Agent",
    "risk_score": 0.2
  },
  "org": {
    "id": "org-uuid",
    "plan": "pro"
  },
  "metadata": {
    "timestamp": "2024-01-15T10:30:00Z",
    "ip_address": "203.0.113.42"
  }
}

Policy Precedence

Decisions follow this precedence (most restrictive wins):

  1. deny - Action is blocked
  2. require_approval - Action needs human approval
  3. allow - Action can proceed

If multiple policies match, the most restrictive decision applies.

Built-in Functions

AAF provides helper functions for common patterns:

Network Security

# Check if URL targets a private network
is_private_ip(url) {
  parts := split(url, "/")
  host := parts[2]
  startswith(host, "10.")
}

is_private_ip(url) {
  parts := split(url, "/")
  host := parts[2]
  startswith(host, "192.168.")
}

is_private_ip(url) {
  parts := split(url, "/")
  host := parts[2]
  startswith(host, "172.16.")
}

Data Classification

# Check if payload contains sensitive data
has_pii(data) {
  regex.match(`\d{3}-\d{2}-\d{4}`, data)  # SSN pattern
}

has_credit_card(data) {
  regex.match(`\d{4}[- ]?\d{4}[- ]?\d{4}[- ]?\d{4}`, data)
}

Time-based Rules

# Deny actions outside business hours
decision = "deny" {
  current_hour := time.clock(time.now_ns())[0]
  current_hour < 9
}

decision = "deny" {
  current_hour := time.clock(time.now_ns())[0]
  current_hour > 17
}

Policy Organization

Policy Packs

Group related policies into packs:

  • Security Pack: SSRF protection, network isolation
  • Compliance Pack: PII detection, audit requirements
  • Financial Pack: Transaction limits, approval thresholds

Policy Versioning

Policies are versioned and changes are tracked:

# View policy history
GET /api/v1/policies/{id}/versions

# Rollback to previous version
POST /api/v1/policies/{id}/rollback
{
  "version": 3
}

Testing Policies

Dry Run Mode

Test policies without executing actions:

const result = await firewall.dryRun({
  tool: 'http_proxy',
  operation: 'POST',
  params: {
    url: 'https://api.stripe.com/v1/charges',
    body: { amount: 50000 }
  }
});

console.log(result.decision);     // "require_approval"
console.log(result.matchedPolicies); // ["financial-limits"]

Policy Playground

The dashboard includes a playground for testing policies:

  1. Navigate to PoliciesPlayground
  2. Enter a sample action
  3. See which policies match and why

Performance

OPA is highly optimized for policy evaluation:

  • Sub-millisecond evaluation for typical policies
  • Caching of compiled policies
  • Parallel evaluation of independent rules

For high-throughput scenarios, consider:

  • Running OPA as a sidecar (Kubernetes)
  • Enabling decision caching (Redis)
  • Simplifying complex regex patterns

Next Steps