Covenant — Policy-Enforced AI Access Control

Covenant is a FastAPI service that enforces OPA Rego policy as a hard gate between JWT identity and Claude Sonnet 4.6. Access is evaluated in the middleware chain before the model runs — a 403 from Rego is a 403, not a suggestion Claude can be prompted around. Three roles (admin, user, auditor) are proven end-to-end with curl demos. pgvector semantic search is scoped by tenant_id, so the AI only ever sees documents it is already permitted to see.

Core Technologies

PythonFastAPIOPAPostgreSQL / pgvectorClaude Sonnet 4.6JWTDocker

Architecture Components

  • FastAPI middleware: validates JWT signature, extracts role/user_id/tenant_id claims
  • OPA REST server (Docker sidecar): evaluates covenant.authz.allow against the input bundle — hard ALLOW or DENY
  • PostgreSQL with pgvector extension: cosine similarity search filtered by tenant_id — cross-tenant results blocked at query level
  • Claude Sonnet 4.6: generates response from permitted documents only, system prompt cached via cache_control: ephemeral
  • Audit log: every OPA decision written with user_id, role, tenant_id, query_hash, docs_returned, opa_decision
  • Docker Compose: OPA server, Postgres+pgvector, and FastAPI start together — $0 to run locally

Problem

AI systems that enforce access control inside the prompt are not enforcing access control — they are asking the model to be a security layer. Any sufficiently creative prompt can talk the model past a soft guard. The question is how to put policy enforcement somewhere Claude cannot touch.

  • Prompt-based access control is not access control — it's a suggestion the model can be prompted around.
  • Multi-tenant AI systems need retrieval isolation, not just response filtering — the model should never see cross-tenant data.
  • Access rules that live in application code or database rows are not auditable, testable, or reviewable the same way code is.

Solution

OPA as middleware, evaluated before the model runs. Rego policies in version control. pgvector retrieval scoped by tenant_id at the query level. Claude only runs on requests that cleared OPA — the AI cannot be a party to the access decision.

  • OPA REST server as a FastAPI middleware dependency: POST input bundle, read result.allow — hard gate in the request path.
  • Rego v1 policies with default allow := false: every identity starts with no access, permissions are explicit grants.
  • pgvector tenant_id filter applied before embedding search — cross-tenant documents never enter the context window.
  • Claude Sonnet 4.6 with prompt caching: system prompt cached via cache_control: ephemeral, near-zero latency on repeated requests.

Outcome

Three-role RBAC proven end-to-end with curl demos. Admin sees everything. User is blocked on sensitive data and cross-tenant queries. Auditor reads the audit log and nothing else. Policy changes are Rego diffs, not code changes.

  • Admin 200, user 403 (sensitive), auditor 403 (query) — all three verified with curl against a running stack.
  • Full audit log: every OPA decision written with who asked, what role, what was allowed or denied.
  • Zero cost to run locally — Docker Compose, OPA sidecar, local Postgres with pgvector.
  • Rego policy is the single source of truth for access rules — reviewable, testable, and version-controlled.

Key Learnings & Decisions

Access Control Design

  • OPA as middleware is the right pattern for AI access control — policy evaluated before the model runs, not inside the prompt.
  • default allow := false is the contract: every identity starts with no access, permissions are explicit grants in Rego.
  • Retrieval scoping at the query level (pgvector tenant_id filter) is stronger than response filtering — the model never sees data it shouldn't.

OPA & Rego

  • OPA has three deployment modes — REST server, Go library, CLI eval. Pick one before writing integration code. Mixing mental models wastes hours.
  • Rego v1 requires import rego.v1 or --v1-compatible flag. Pin your OPA image version. The error messages for version mismatches are not obvious.
  • Mount policies as a Docker volume so edits apply without rebuilding the container — fast iteration during policy development.

Database & Docker

  • CREATE EXTENSION vector requires superuser. Split init.sql into numbered files: 00_superuser.sql (postgres user) and 01_app.sql (app user). PostgreSQL processes initdb scripts alphabetically.
  • Use the official pgvector/pgvector:pg16 image — prebuilt binaries, no kernel header issues on WSL2 or CI runners.
  • Separate demo token TTL from production token TTL in config from day one. Auth debugging during a live demo is the worst kind of debugging.

Implementation Milestones

A breakdown of the key tasks and milestones that brought this project to life.

JWT + OPA Integration

Complete

FastAPI middleware validates JWT and POSTs input bundle to OPA REST server. Hard 403 on policy deny. Three-role Rego policy written with default-deny base.

Key Tasks Completed

  • JWT Middleware

    Validates signature, extracts claims, returns 401 on invalid token before OPA is called.

  • OPA REST Integration

    FastAPI POSTs input bundle to OPA sidecar. result.allow false returns 403 immediately.

  • Rego v1 Policy

    Three rules: admin full access, user non-sensitive own-tenant, auditor audit-log only. import rego.v1 and OPA version pinned.

pgvector Tenant Isolation

Complete

PostgreSQL with pgvector extension. Documents table with tenant_id column. Cosine similarity search filtered at query level — cross-tenant results impossible.

Key Tasks Completed

  • Postgres Init Scripts

    Split into 00_superuser.sql (CREATE EXTENSION vector as postgres) and 01_app.sql (schema as app user). Alphabetical ordering enforced.

  • pgvector Tenant-Scoped Search

    Embedding search with WHERE tenant_id = claims.tenant_id. Used official pgvector/pgvector:pg16 image to avoid WSL2 build issues.

Claude Integration + Prompt Caching

Complete

Claude Sonnet 4.6 receives permitted documents and user query. System prompt cached with cache_control: ephemeral.

Key Tasks Completed

  • Claude API Integration

    Structured prompt with permitted documents. cache_control: ephemeral on system prompt. Claude only called after OPA allow.

Three-Role RBAC Demo

Complete

End-to-end proof: admin 200, user 403 on sensitive query, auditor 403 on query endpoint. Audit log verified.

Key Tasks Completed

  • RBAC End-to-End Demo

    All three roles tested with curl. Admin gets response. User blocked on sensitive flag. Auditor blocked on /query entirely. Audit log has all three decisions.

  • JWT Token Helper

    get_token() checks exp claim and auto-refreshes. Demo mode uses 24-hour TTL from .env.

Case Study Published

Complete

Standalone case study published to adventuringghost.com/publish/covenant-case-study.html.

Key Tasks Completed

  • Portfolio Entry

    Standalone HTML case study written and deployed.

Monitoring & Analysis

OPA Decision Log

Every policy evaluation is written to the audit log with user_id, role, tenant_id, path, query_hash, documents_returned, and the OPA decision (allow/deny). Auditors can query this log. They cannot query documents.

Claude API Usage

Prompt caching via cache_control: ephemeral on the system prompt. Cache hit rate visible in the Anthropic API response headers. Repeated requests in the same session return significantly faster at lower cost.

Covenant query handler — OPA gate before Claude runs

Loading code...

Part of a larger arc

The AI Security & Resilience Stack

Three independent projects that together cover the full surface of an AI-augmented infrastructure stack. Warden secures the Kubernetes runtime — Falco and OPA detecting threats as they happen, Claude triaging before an engineer is paged. Covenant controls access at the application layer — OPA as the hard gate between JWT identity and Claude, policy in code not prompts. Watershed closes the loop at the edge — async telemetry buffered through connectivity loss, with Claude flagging anomalies before the data reaches the cloud. Each project stands alone; together they tell one story.

~$2.00

Warden (AKS)

$0.00

Covenant (local Docker)

~$0.05

Watershed (AWS IoT Core)

~$2.05

Combined