Ai Policy As Code: OPA Checks Before Every Shell Execution

Policy-as-Code for AI: OPA Checks Before Every Shell Execution
14 views

This guide covers AI policy as code with production-focused examples and trade-offs.

Allowlists help; OPA makes the rules reviewable, testable, and versioned. Every agent shell proposal becomes an input document—deny metadata IPs, kubectl delete, force-push, and naked aws s3 sync before the process starts.
⚡ TL;DR: Wrap the shell tool with an OPA/Rego gate. Pass argv, cwd, env fingerprints, and actor; deny by default; unit-test policies in CI. Elevation requires dual-control. Pair with Agent Tool Allowlists and Claude Code Hooks.

Input document — AI policy as code

json
{
  "argv": ["curl", "-s", "http://169.254.169.254/latest/meta-data/"],
  "cwd": "/workspace/app",
  "actor": "agent:pr-4421",
  "elevation": false,
  "network": true
}

Rego that fails closed

rego
package agent.shell

default allow := false

dangerous_host if {
  some i
  host := input.argv[i]
  host == "169.254.169.254"
}

dangerous_host if {
  some i
  startswith(input.argv[i], "http://169.254.169.254")
}

deny[msg] if {
  dangerous_host
  msg := "metadata_ip_blocked"
}

deny[msg] if {
  input.argv[0] == "kubectl"
  some i
  input.argv[i] == "delete"
  not input.elevation
  msg := "kubectl_delete_needs_elevation"
}

deny[msg] if {
  input.argv[0] == "aws"
  input.argv[1] == "s3"
  input.argv[2] == "sync"
  not startswith(input.argv[3], "s3://my-org-scratch/")
  msg := "s3_sync_unscoped"
}

allow if {
  count(deny) == 0
  input.argv[0] in {"pnpm", "npm", "pytest", "go", "cargo", "git"}
}
✅ Default deny + explicit command allowlist.
❌ “Permit all except a few regexes” maintained in prompt text only.

Hook integration

typescript
// beforeShell(cmd)
const decision = await opa.evaluate("agent/shell/allow", {
  argv: parse(cmd),
  cwd: process.cwd(),
  actor: session.actor,
  elevation: session.elevated,
});
if (!decision.result) {
  throw new Error(`opa_deny:${decision.denyReasons.join(",")}`);
}
Same placement as Claude Code / Cursor hooks—policy is code, not chat. For IAM mutations specifically, still run Access Analyzer gates.

Policy CI

bash
opa test ./policy/agent -v
conftest verify --policy policy/agent fixtures/shell/*.json
Break the build when a fixture that should deny suddenly allows.

Elevation and audit

Elevation is a separate Rego path, not a prompt wink. Require input.elevation == true and a change ticket ID that exists in your tracker API before allowing kubectl delete or cloud destructive verbs. Emit every decision to CloudWatch / OpenSearch with argv hash, actor, and policy version SHA so postmortems can answer “why did the agent run this?”
typescript
type ShellDecision = {
  allow: boolean;
  reasons: string[];
  policySha: string;
  argvHash: string;
  actor: string;
  ticketId?: string;
};

// Persist deny and allow — allows matter for forensics too
await logDecision(decision);
Pair elevation UX with AI On-Call Copilots so Slack approval and OPA elevation stay in sync.

Closing checklist

  • [ ] OPA bundled beside the agent runner; offline eval (no remote PDP required for deny path)
  • [ ] Unit tests for metadata IP, kubectl delete, force-push, curl file://
  • [ ] Elevation path audited with ticket ID
  • [ ] Decisions logged with argv hash (redact secrets)
  • [ ] Policy PRs require security CODEOWNERS
  • [ ] Prompt text never overrides OPA

Related reading

Last updated on September 11, 2026

Related: AI Dependency Upgrades: Compatibility Matrices Before Import Rewrites · Day 93: SLOs for AI Features · Day 45: Catastrophic Forgetting Checks

Watch: 100 Days of AI on YouTube

Short videos from CheatCoders, one AI engineering topic per day.

Open the playlist on YouTube · CheatCoders on YouTube

Deep-dive PDF

Get the expanded guide for this post — extra diagrams-style checklists, failure modes, and a production walkthrough. Free when you subscribe to CheatCoders.

Already subscribed? or open the subscribe page.


Discover more from CheatCoders

Subscribe to get the latest posts sent to your email.

Comments

No comments yet. Why don’t you start the discussion?

Leave a comment

No account needed. Name and email are optional.