DocumentationAgent Action Firewall

Policies as Code (GitOps)

Author policies in your own repository — as rule bundles or code-owned Rego — and drive validation, tests, and apply from CI using an agent API key.

Authoring modes

Every policy has an authoring_mode: rules (the default — the canonical enforced model), workflow (a visual canvas compiled server-side into rules), or rego (a Rego module evaluated directly by the OPA sidecar). All modes run in the org_policy tier, which evaluates your org's policies in priority order before the built-in packs; the first match wins and no match falls through.

The Rego decision contract

A rego-mode policy is a Rego module that must bind decision. The evaluator queries it on every action and interprets the result as:

  • {"action": "allow" | "deny" | "require_approval", "reason": string} — the policy matched; this action and reason become the org-policy outcome.
  • undefined — the module produced no decision; evaluation falls through to the next policy and then the built-in tiers.

Anything else (a boolean, a string, a malformed object) is treated as no match.

Go
import rego.v1

# The package line is rewritten server-side — the value you write is ignored.
package my.team.safety

# Destructive operations on production databases require approval.
decision := {"action": "require_approval", "reason": "prod destructive op needs approval"} if {
    input.operation == "delete"
    input.params.environment == "production"
}

# PII exports over a row threshold are denied outright.
decision := {"action": "deny", "reason": "bulk PII export denied"} if {
    input.tool == "database"
    input.operation == "export"
    input.params.row_count > 10000
}

Input document

Org policies receive the same input shape as the built-in packs:

Go
input.tool        # e.g. "database", "stripe", "http_proxy"
input.operation   # e.g. "get", "delete", "create_charge"
input.params      # action parameters (access via input.params.<field>)
input.agent       # {id, name}
input.org         # {id}
input.context     # org context variables (IP, timestamps, custom fields)

Org isolation

Modules are loaded into the OPA sidecar under a server-derived package aaf.org.o_<org>.p_<policy> and evaluated at /v1/data/aaf/org/o_<org>/p_<policy>/decision. Your own package line is ignored — one org can never import or shadow another org's rules, and modules cannot reference the built-in packs by name collisions because the package is fully org-prefixed.

Loading, caching, and failure behavior

  • Modules load lazily on first evaluation, cached by policy id + content hash.
  • After an OPA restart the module is transparently re-uploaded.
  • Compile errors are returned at save/publish time (HTTP 422 with diagnostics) — a broken module can't silently ship.
  • If OPA is unreachable at decision time, the tier follows the org's cascade mode: fail_open (default) falls through to DB rules/heuristics; fail_closed denies for enforced policies.
  • watch-state policies evaluate but never decide — their outcomes are recorded as shadow decisions.

Import / export

A policy exports as a portable bundle — name, description, authoring_mode, rules, plus workflow, rego, and test_cases when present. Rules that evaluate through OPA carry their module in a rego_source field.

Bash
# Export (JSON — the default, or format=yaml)
curl -H "Authorization: Bearer $JWT" \
  "$AAF_API/admin/policies/<policy_id>/export?format=json" > policy.json

# Import (always creates a new policy in draft)
curl -X POST -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" \
  --data-binary @policy.json \
  "$AAF_API/admin/policies/import"

The dashboard has matching Export actions on the policy list and policy detail pages, and an Import button on the policy list.

GitOps endpoints (agent API key)

CI uses an agent/admin API key in the X-Agent-Key header — the same credential agents use for action submission, scoped to one org.

Bash
AAF_API=https://api.agentactionfirewall.com

# 1. Validate — Zod schema checks + OPA compile check for rego bundles.
#    200 {valid, diagnostics[]} | 400 schema error | 422 compile diagnostics
curl -X POST -H "X-Agent-Key: $AAF_API_KEY" -H "Content-Type: application/json" \
  --data-binary '{"policy": <bundle>}' "$AAF_API/v1/policies/validate"

# 2. Test — runs the bundle's test_cases without persisting.
#    200 {passed, total, results[]}
curl -X POST -H "X-Agent-Key: $AAF_API_KEY" -H "Content-Type: application/json" \
  --data-binary '{"policy": <bundle>}' "$AAF_API/v1/policies/test"

# 3. Apply — upsert by name. Creates a draft; "state" moves it to
#    watch/enforced explicitly.
curl -X POST -H "X-Agent-Key: $AAF_API_KEY" -H "Content-Type: application/json" \
  --data-binary '{"policy": <bundle>, "state": "watch"}' \
  "$AAF_API/v1/policies/apply"

Example GitHub Actions workflow

Validate and test on every PR, apply on merge to main. Store the API key as a repository secret (AAF_API_KEY).

YAML
name: policies
on:
  pull_request:
    paths: ['policies/**']
  push:
    branches: [main]
    paths: ['policies/**']

jobs:
  policy-ci:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Validate + test every bundle
        env:
          AAF_API_KEY: ${{ secrets.AAF_API_KEY }}
          AAF_API: https://api.agentactionfirewall.com
        run: |
          for f in policies/*.json; do
            echo "== $f"
            # validate (fail on any error-severity diagnostic)
            valid=$(curl -sf -X POST -H "X-Agent-Key: $AAF_API_KEY" \
              -H "Content-Type: application/json" \
              --data-binary "{\"policy\": $(cat "$f")}" \
              "$AAF_API/v1/policies/validate" | jq '.valid')
            [ "$valid" = "true" ] || { echo "validation failed: $f"; exit 1; }
            # run test cases
            curl -sf -X POST -H "X-Agent-Key: $AAF_API_KEY" \
              -H "Content-Type: application/json" \
              --data-binary "{\"policy\": $(cat "$f")}" \
              "$AAF_API/v1/policies/test" | jq '{passed, total}'
          done

      - name: Apply bundles (main only)
        if: github.ref == 'refs/heads/main' && github.event_name == 'push'
        env:
          AAF_API_KEY: ${{ secrets.AAF_API_KEY }}
          AAF_API: https://api.agentactionfirewall.com
        run: |
          for f in policies/*.json; do
            curl -sf -X POST -H "X-Agent-Key: $AAF_API_KEY" \
              -H "Content-Type: application/json" \
              --data-binary "{\"policy\": $(cat "$f"), \"state\": \"watch\"}" \
              "$AAF_API/v1/policies/apply"
          done

Applied bundles land in draft (or the state you pass). Promote to watch to shadow-evaluate against live traffic, then to enforced when the recorded outcomes look right — see Policy Basics.