Skip to content

Chapter 4.7 — Policy-as-code

🧠 Concept

Critical policies are codified, testable and versioned. Examples:

  • "An agent cannot send external email without dual approval."
  • "A CRM tool only accesses fields permitted by role."
  • "Confidential documents do not go to an external model without a contract."

🧰 Common engines

  • Open Policy Agent (OPA) / Rego — a general-purpose declarative engine, used in Kubernetes, microservices, gateways and agents.
  • AWS Cedar — a policy language focused on authorization, with "policy + entity store" semantics.
  • AuthZed SpiceDB — distributed ReBAC, a model inspired by Zanzibar (Google).

🧪 Practical example — authorizing an agent tool in Rego

Scenario: a harness calls OPA passing the input below before executing any tool_call. The policy must:

  • deny a tool with status deprecated or removed;
  • deny cross-tenant (user.tenant_id != resource.tenant_id);
  • require human approval for risk_level == "high";
  • allow only tools in the role's allowlist.

Typical input sent by the executor:

{
  "user": {
    "id": "u-1042",
    "tenant_id": "tenant-abc",
    "roles": ["support-agent"]
  },
  "agent": {
    "id": "support-agent@2.1.0",
    "tenant_id": "tenant-abc"
  },
  "tool": {
    "name": "crm.update_contact",
    "version": "1.4.0",
    "status": "active",
    "risk_level": "high"
  },
  "action": "execute",
  "resource": {
    "type": "contact",
    "tenant_id": "tenant-abc"
  },
  "approval": {
    "required": true,
    "granted_by": "manager-7",
    "granted_at": "2026-05-10T18:22:00Z"
  }
}

Rego policy (educational example):

package agent.tools.authz

default allow := false

# Allowlist of tools per role.
allowed_tools := {
  "support-agent": {
    "crm.search_contact",
    "crm.update_contact",
    "kb.search",
  },
  "billing-agent": {
    "crm.search_contact",
    "billing.create_invoice",
  },
}

# Tools with a forbidden status can never run.
blocked_status := {"deprecated", "removed", "blocked"}

# Helper: is the tool in the allowlist of the user's role?
tool_in_allowlist {
  some role
  role := input.user.roles[_]
  allowed_tools[role][input.tool.name]
}

# Helper: user tenant == agent tenant == resource tenant.
same_tenant {
  input.user.tenant_id == input.agent.tenant_id
  input.user.tenant_id == input.resource.tenant_id
}

# Helper: valid human approval for high risk.
approval_ok {
  input.tool.risk_level != "high"
}

approval_ok {
  input.tool.risk_level == "high"
  input.approval.required
  input.approval.granted_by != ""
}

# Main rule.
allow {
  not blocked_status[input.tool.status]
  tool_in_allowlist
  same_tenant
  approval_ok
}

# Denial reasons (useful for the audit log).
deny_reason["tool_status_blocked"] {
  blocked_status[input.tool.status]
}

deny_reason["tool_not_in_allowlist"] {
  not tool_in_allowlist
}

deny_reason["cross_tenant"] {
  not same_tenant
}

deny_reason["approval_missing"] {
  input.tool.risk_level == "high"
  not input.approval.granted_by
}

What this policy guarantees:

  • Blocks a deprecated/removed tool regardless of what the model proposes.
  • Blocks a cross-tenant call even if the prompt said otherwise.
  • Blocks a tool outside the role's allowlist.
  • Requires recorded human approval for risk_level == "high".
  • Returns structured reasons for the audit log.

What this policy does NOT guarantee:

  • That the content of the arguments is valid (needs schema/Pydantic).
  • That the human approval was conscious (depends on UX and training).
  • That the tool's backend applies internal RBAC (the called service also needs authorization — defense in depth).
  • That the input sent to OPA is honest (depends on the harness; whoever controls the input controls the decision).

🧪 Policy tests

Every policy needs positive and negative tests versioned in the same repo. In OPA, this can be done with opa test and *_test.rego files:

package agent.tools.authz_test

import data.agent.tools.authz

test_allow_simple_case {
  authz.allow with input as {
    "user": {"id": "u1", "tenant_id": "t1", "roles": ["support-agent"]},
    "agent": {"id": "a@1", "tenant_id": "t1"},
    "tool": {"name": "kb.search", "status": "active", "risk_level": "low"},
    "action": "execute",
    "resource": {"type": "doc", "tenant_id": "t1"},
    "approval": {"required": false, "granted_by": "", "granted_at": ""}
  }
}

test_deny_cross_tenant {
  not authz.allow with input as {
    "user": {"id": "u1", "tenant_id": "t1", "roles": ["support-agent"]},
    "agent": {"id": "a@1", "tenant_id": "t1"},
    "tool": {"name": "kb.search", "status": "active", "risk_level": "low"},
    "action": "execute",
    "resource": {"type": "doc", "tenant_id": "t2"},
    "approval": {"required": false, "granted_by": "", "granted_at": ""}
  }
}

test_deny_deprecated_tool {
  not authz.allow with input as {
    "user": {"id": "u1", "tenant_id": "t1", "roles": ["support-agent"]},
    "agent": {"id": "a@1", "tenant_id": "t1"},
    "tool": {"name": "crm.update_contact", "status": "deprecated", "risk_level": "high"},
    "action": "execute",
    "resource": {"type": "contact", "tenant_id": "t1"},
    "approval": {"required": true, "granted_by": "m7"}
  }
}
  • EX-AGT-06 — an agent with a Rego policy sidecar blocking tool calls.
  • EX-SEC-03 — tool misuse mitigated by a policy.

📌 Checklist

  • [ ] Are critical policies in code and versioned?
  • [ ] Are there policy tests (positive and negative)?
  • [ ] Is there observability of policy decisions (decision + reason)?
  • [ ] Are decisions logged with the policy bundle version?
  • [ ] Does the backend called by the tools apply independent authorization (defense in depth)?

📚 References