From 602b0683804fd75b5fc5d709127cce384104ddbd Mon Sep 17 00:00:00 2001 From: Beon de Nood Date: Sat, 21 Mar 2026 03:01:25 -0400 Subject: [PATCH 1/6] docs: add embedded PDP documentation - Policy enforcement guide now covers both embedded and external PDP - Embedded PDP config: CAPISCIO_EMBEDDED_PDP, BUNDLE_POLL_INTERVAL, BUNDLE_STALENESS_THRESHOLD - Bundle staleness behavior per enforcement mode - Bundle endpoint documentation (/v1/bundles/{workspace_id}) - Server reference updated to list embedded PDP feature --- docs/how-to/security/policy-enforcement.md | 271 +++++++++++++++++++++ docs/reference/server/index.md | 1 + 2 files changed, 272 insertions(+) create mode 100644 docs/how-to/security/policy-enforcement.md diff --git a/docs/how-to/security/policy-enforcement.md b/docs/how-to/security/policy-enforcement.md new file mode 100644 index 0000000..c1ba2b8 --- /dev/null +++ b/docs/how-to/security/policy-enforcement.md @@ -0,0 +1,271 @@ +--- +title: Policy Enforcement Setup +description: Configure PDP integration to add authorization decisions to your agent +--- + +# Policy Enforcement Setup + +Add authorization decisions to badge-verified requests by connecting to a Policy Decision Point (PDP). + +--- + +## Problem + +You need to: + +- Control which agents can access which endpoints based on policy +- Enforce rate limits, field redaction, or audit logging per-agent +- Gradually roll out policy enforcement without breaking existing traffic +- Override policy in emergencies + +--- + +## Solution + +Configure the CapiscIO server to enforce policy decisions. Two PDP modes are available: + +| Mode | Description | Use When | +|------|-------------|----------| +| **Embedded PDP** | In-process OPA evaluator; zero-latency policy queries | Single-node deployments, fast iteration, no external dependencies | +| **External PDP** | Remote HTTP PDP (any OPA/Cedar/custom endpoint) | Multi-node fleets, existing PDP infrastructure, custom engines | + +--- + +## Option A: Embedded PDP (Recommended) + +The embedded PDP runs an OPA evaluator inside the server process. It automatically builds a policy bundle from your registry data (agents, MCP servers) and a built-in starter policy. + +### Prerequisites + +- A running CapiscIO server with badge verification enabled +- `CAPISCIO_WORKSPACE` set to your workspace/org UUID + +### Step 1: Enable the Embedded PDP + +```bash +export CAPISCIO_EMBEDDED_PDP=true +export CAPISCIO_WORKSPACE= +export CAPISCIO_ENFORCEMENT_MODE=EM-OBSERVE +``` + +The server starts an in-process OPA evaluator with: + +- A **starter Rego policy** that allows requests from registered agents with active status +- A **policy bundle** auto-built from registry data (agents, MCP servers, workspace policies) +- **Hybrid rebuild**: timer-based polling (default 30s) plus event-driven rebuild on registry changes + +### Step 2: Review Decisions + +Check server logs for policy events. Each request emits: + +| Field | Description | +|-------|-------------| +| `capiscio.policy.decision` | `ALLOW`, `DENY`, or `ALLOW_OBSERVE` | +| `capiscio.policy.decision_id` | Unique evaluation ID | +| `staleness.bundle_stale` | `true` if the policy bundle is stale | +| `staleness.bundle_age_ms` | Age of the bundle in milliseconds (when stale) | + +Verify that legitimate requests receive `ALLOW` and unauthorized access patterns receive `DENY` before tightening the enforcement mode. + +### Step 3: Tighten Enforcement + +Once decisions look correct, move through the enforcement modes: + +```bash +# Allow but log — PDP decisions are informational +export CAPISCIO_ENFORCEMENT_MODE=EM-OBSERVE + +# Deny unauthorized — PDP DENY blocks requests (fail-closed on PDP failure) +export CAPISCIO_ENFORCEMENT_MODE=EM-GUARD + +# Full enforcement — unknown obligation types also cause denial +export CAPISCIO_ENFORCEMENT_MODE=EM-STRICT +``` + +### Bundle Staleness + +When the embedded PDP's policy bundle hasn't been rebuilt within the staleness threshold: + +| Mode | Behaviour | +|------|-----------| +| `EM-OBSERVE` | Request proceeds; `bundle_stale` annotation in telemetry | +| `EM-GUARD` | Request proceeds; `bundle_stale` annotation in telemetry | +| `EM-STRICT` | Request denied with `BUNDLE_STALE` error code | + +Configure staleness via: + +```bash +export CAPISCIO_BUNDLE_STALENESS_THRESHOLD=5m # default: 5m +export CAPISCIO_BUNDLE_POLL_INTERVAL=30s # default: 30s +``` + +### Bundle Endpoint + +When the embedded PDP is active, a bundle endpoint is available for external OPA consumers: + +``` +GET /v1/bundles/{workspace_id} +``` + +This requires `X-Capiscio-Registry-Key` authentication and the API key must belong to the matching workspace/org. + +--- + +## Option B: External PDP + +Connect to any PDP that accepts HTTP POST requests with PIP-format decision requests (RFC-005 §5.1). + +### Prerequisites + +- A running CapiscIO server with badge verification enabled +- A PDP that accepts HTTP POST requests + +### Step 1: Start in Observe Mode + +Begin with `EM-OBSERVE` to monitor PDP decisions without affecting traffic: + +```bash +export CAPISCIO_PDP_ENDPOINT=http://localhost:9090/v1/evaluate +export CAPISCIO_ENFORCEMENT_MODE=EM-OBSERVE +export CAPISCIO_PDP_TIMEOUT_MS=500 +``` + +In this mode, PDP DENY decisions are logged but requests proceed. If the PDP is unreachable, requests also proceed with an `ALLOW_OBSERVE` telemetry marker. + +Restart the server and monitor logs for `capiscio.policy_enforced` events. + +--- + +## Step 2: Review Decisions + +Check server logs for policy events. Each request that passes through the PEP emits: + +| Field | Description | +|-------|-------------| +| `capiscio.policy.decision` | `ALLOW`, `DENY`, or `ALLOW_OBSERVE` | +| `capiscio.policy.decision_id` | Unique ID from the PDP | +| `capiscio.policy.error_code` | `PDP_UNAVAILABLE` if PDP was unreachable | + +Verify that legitimate requests receive `ALLOW` and unauthorized access patterns receive `DENY` before tightening the enforcement mode. + +--- + +## Step 3: Tighten Enforcement + +Once decisions look correct, switch to `EM-GUARD`: + +```bash +export CAPISCIO_ENFORCEMENT_MODE=EM-GUARD +``` + +Now PDP DENY decisions block requests with `403 Forbidden`. If the PDP is unavailable, requests are denied with `503 Service Unavailable` (fail-closed). + +For full obligation enforcement, use `EM-STRICT`: + +```bash +export CAPISCIO_ENFORCEMENT_MODE=EM-STRICT +``` + +In EM-STRICT, unknown obligation types cause the request to be denied. + +--- + +## Step 4: Configure Break-Glass (Optional) + +For emergency access when the PDP is down or mis-configured: + +1. Generate a dedicated Ed25519 keypair for break-glass tokens: + + ```bash + capiscio keygen --output breakglass-key + ``` + +2. Configure the server with the public key path: + + ```bash + export CAPISCIO_BREAKGLASS_PUBLIC_KEY=/etc/capiscio/breakglass-key.pub.pem + ``` + +3. In an emergency, issue a break-glass token and include it as the `X-Capiscio-Breakglass` header. The token must contain a `reason`, scoped `methods`/`routes`, and a short expiry. + +!!! warning + Break-glass bypasses authorization but **not** authentication. The request must still carry a valid badge. + +--- + +## Configuration Reference + +All PDP-related environment variables: + +| Variable | Default | Description | +|----------|---------|-------------| +| `CAPISCIO_EMBEDDED_PDP` | `false` | Enable embedded OPA evaluator (in-process PDP) | +| `CAPISCIO_PDP_ENDPOINT` | _(empty)_ | External PDP URL. Empty + no embedded PDP = badge-only mode | +| `CAPISCIO_PDP_TIMEOUT_MS` | `500` | External PDP query timeout in milliseconds | +| `CAPISCIO_ENFORCEMENT_MODE` | `EM-OBSERVE` | One of: `EM-OBSERVE`, `EM-GUARD`, `EM-DELEGATE`, `EM-STRICT` | +| `CAPISCIO_WORKSPACE` | _(empty)_ | Workspace/tenant UUID (required for embedded PDP) | +| `CAPISCIO_BUNDLE_POLL_INTERVAL` | `30s` | Embedded PDP bundle rebuild interval | +| `CAPISCIO_BUNDLE_STALENESS_THRESHOLD` | `5m` | Embedded PDP bundle age before staleness warnings | +| `CAPISCIO_BREAKGLASS_PUBLIC_KEY` | _(empty)_ | Path to break-glass Ed25519 public key file | +| `CAPISCIO_PEP_ID` | _(empty)_ | PEP instance identifier (sent to PDP as `X-Capiscio-PEP-ID`) | + +--- + +## PDP Request Format + +The PEP sends a JSON POST to the PDP endpoint. Your PDP must accept this format: + +```json +{ + "pip_version": "capiscio.pip.v1", + "subject": { + "did": "did:web:agent.example.com", + "badge_jti": "badge-uuid", + "ial": "1", + "trust_level": "DV" + }, + "action": { + "operation": "POST /api/v1/badges", + "capability_class": null + }, + "resource": { + "identifier": "/api/v1/badges" + }, + "context": { + "txn_id": "txn-uuid", + "enforcement_mode": "EM-GUARD" + }, + "environment": { + "workspace": "production", + "pep_id": "server-01", + "time": "2026-03-01T12:00:00Z" + } +} +``` + +And return: + +```json +{ + "decision": "ALLOW", + "decision_id": "eval-uuid", + "obligations": [], + "reason": "Policy matched: allow-trusted-agents", + "ttl": 300 +} +``` + +--- + +## Verification + +Confirm policy enforcement is active: + +```bash +# Send a request and check response headers +curl -v https://your-server/api/v1/badges \ + -H "X-Capiscio-Badge: $BADGE_JWS" + +# Look for Server-Timing header with policy timing +# Server-Timing: capiscio-auth;dur=0.6, capiscio-policy;dur=12.3 +``` diff --git a/docs/reference/server/index.md b/docs/reference/server/index.md index 6125454..6b50d68 100644 --- a/docs/reference/server/index.md +++ b/docs/reference/server/index.md @@ -14,6 +14,7 @@ capiscio-server provides: - **Agent Registry** — CRUD operations for agent records - **Badge CA** — Certificate Authority for issuing trust badges (levels 1-4) +- **Embedded PDP** — In-process OPA policy evaluator with auto-built bundles - **JWKS Endpoint** — Public key set for badge verification - **DID Resolution** — `did:web` document serving for registered agents - **API Key Auth** — Secure API access management From bbb5e91c78c24ce96ccb3813ff7c0b0d68993297 Mon Sep 17 00:00:00 2001 From: Beon de Nood Date: Sat, 21 Mar 2026 03:09:46 -0400 Subject: [PATCH 2/6] fix: address Copilot review comments on docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix heading hierarchy in external PDP section (## → ###) - Add nav entry for policy-enforcement.md in mkdocs.yml - Add RFC-005 link in external PDP section - Clarify event field naming consistency - Fix 'Behaviour' → 'Behavior' spelling - Add trust_level format clarification (DV/OV/EV maps to IAL) --- docs/how-to/security/policy-enforcement.md | 14 +++++++------- mkdocs.yml | 1 + 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/how-to/security/policy-enforcement.md b/docs/how-to/security/policy-enforcement.md index c1ba2b8..a0b152f 100644 --- a/docs/how-to/security/policy-enforcement.md +++ b/docs/how-to/security/policy-enforcement.md @@ -86,7 +86,7 @@ export CAPISCIO_ENFORCEMENT_MODE=EM-STRICT When the embedded PDP's policy bundle hasn't been rebuilt within the staleness threshold: -| Mode | Behaviour | +| Mode | Behavior | |------|-----------| | `EM-OBSERVE` | Request proceeds; `bundle_stale` annotation in telemetry | | `EM-GUARD` | Request proceeds; `bundle_stale` annotation in telemetry | @@ -113,7 +113,7 @@ This requires `X-Capiscio-Registry-Key` authentication and the API key must belo ## Option B: External PDP -Connect to any PDP that accepts HTTP POST requests with PIP-format decision requests (RFC-005 §5.1). +Connect to any PDP that accepts HTTP POST requests with PIP-format decision requests ([RFC-005 §5.1](https://github.com/capiscio/capiscio-rfcs)). ### Prerequisites @@ -132,11 +132,11 @@ export CAPISCIO_PDP_TIMEOUT_MS=500 In this mode, PDP DENY decisions are logged but requests proceed. If the PDP is unreachable, requests also proceed with an `ALLOW_OBSERVE` telemetry marker. -Restart the server and monitor logs for `capiscio.policy_enforced` events. +Restart the server and monitor logs for `capiscio.policy_enforced` events. Each event contains a `payload.policy.decision` field with the PDP's verdict. --- -## Step 2: Review Decisions +### Step 2: Review Decisions Check server logs for policy events. Each request that passes through the PEP emits: @@ -150,7 +150,7 @@ Verify that legitimate requests receive `ALLOW` and unauthorized access patterns --- -## Step 3: Tighten Enforcement +### Step 3: Tighten Enforcement Once decisions look correct, switch to `EM-GUARD`: @@ -170,7 +170,7 @@ In EM-STRICT, unknown obligation types cause the request to be denied. --- -## Step 4: Configure Break-Glass (Optional) +### Step 4: Configure Break-Glass (Optional) For emergency access when the PDP is down or mis-configured: @@ -222,7 +222,7 @@ The PEP sends a JSON POST to the PDP endpoint. Your PDP must accept this format: "did": "did:web:agent.example.com", "badge_jti": "badge-uuid", "ial": "1", - "trust_level": "DV" + "trust_level": "DV" // Trust level code (DV/OV/EV) — maps to IAL 1/2/3 }, "action": { "operation": "POST /api/v1/badges", diff --git a/mkdocs.yml b/mkdocs.yml index 0b0ae79..c0d7b3c 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -216,6 +216,7 @@ nav: - Verify Inbound: how-to/security/verify-inbound.md - Trust Badges: how-to/security/badges.md - Badge Keeper: how-to/security/badge-keeper.md + - Policy Enforcement: how-to/security/policy-enforcement.md - Security Gateway: how-to/security/gateway-setup.md - Ephemeral Deployment: how-to/security/ephemeral-deployment.md - Dev Mode: how-to/security/dev-mode.md From 93d9b64d94948852a74885ad933b45744706522f Mon Sep 17 00:00:00 2001 From: Beon de Nood Date: Sat, 21 Mar 2026 03:18:02 -0400 Subject: [PATCH 3/6] fix: address Copilot review round 2 comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Clarify capiscio.policy_enforced event → capiscio.policy.* field mapping - Remove undocumented EM-DELEGATE from enforcement mode list - Use UUID format for workspace in PDP request example --- docs/how-to/security/policy-enforcement.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/how-to/security/policy-enforcement.md b/docs/how-to/security/policy-enforcement.md index a0b152f..b4fe457 100644 --- a/docs/how-to/security/policy-enforcement.md +++ b/docs/how-to/security/policy-enforcement.md @@ -132,7 +132,7 @@ export CAPISCIO_PDP_TIMEOUT_MS=500 In this mode, PDP DENY decisions are logged but requests proceed. If the PDP is unreachable, requests also proceed with an `ALLOW_OBSERVE` telemetry marker. -Restart the server and monitor logs for `capiscio.policy_enforced` events. Each event contains a `payload.policy.decision` field with the PDP's verdict. +Restart the server and monitor logs for policy enforcement events (the event type is `capiscio.policy_enforced`). Each event flattens the PDP verdict into the `capiscio.policy.*` fields described below, including `capiscio.policy.decision`. --- @@ -202,7 +202,7 @@ All PDP-related environment variables: | `CAPISCIO_EMBEDDED_PDP` | `false` | Enable embedded OPA evaluator (in-process PDP) | | `CAPISCIO_PDP_ENDPOINT` | _(empty)_ | External PDP URL. Empty + no embedded PDP = badge-only mode | | `CAPISCIO_PDP_TIMEOUT_MS` | `500` | External PDP query timeout in milliseconds | -| `CAPISCIO_ENFORCEMENT_MODE` | `EM-OBSERVE` | One of: `EM-OBSERVE`, `EM-GUARD`, `EM-DELEGATE`, `EM-STRICT` | +| `CAPISCIO_ENFORCEMENT_MODE` | `EM-OBSERVE` | One of: `EM-OBSERVE`, `EM-GUARD`, `EM-STRICT` | | `CAPISCIO_WORKSPACE` | _(empty)_ | Workspace/tenant UUID (required for embedded PDP) | | `CAPISCIO_BUNDLE_POLL_INTERVAL` | `30s` | Embedded PDP bundle rebuild interval | | `CAPISCIO_BUNDLE_STALENESS_THRESHOLD` | `5m` | Embedded PDP bundle age before staleness warnings | @@ -236,7 +236,7 @@ The PEP sends a JSON POST to the PDP endpoint. Your PDP must accept this format: "enforcement_mode": "EM-GUARD" }, "environment": { - "workspace": "production", + "workspace": "00000000-0000-0000-0000-000000000000", "pep_id": "server-01", "time": "2026-03-01T12:00:00Z" } From dc0dc76f1e59e777189a04c7416d3431a5ea5ecd Mon Sep 17 00:00:00 2001 From: Beon de Nood Date: Sat, 21 Mar 2026 03:26:13 -0400 Subject: [PATCH 4/6] fix: address Copilot review round 3 comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Use staleness.bundle_stale consistently in staleness table - Fix spelling: mis-configured → misconfigured - Fix CLI command: capiscio keygen → capiscio key gen - Use jsonc fence for JSON with comments - Fix /api/v1/ → /v1/ path consistency in examples --- docs/how-to/security/policy-enforcement.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/how-to/security/policy-enforcement.md b/docs/how-to/security/policy-enforcement.md index b4fe457..f1576df 100644 --- a/docs/how-to/security/policy-enforcement.md +++ b/docs/how-to/security/policy-enforcement.md @@ -88,8 +88,8 @@ When the embedded PDP's policy bundle hasn't been rebuilt within the staleness t | Mode | Behavior | |------|-----------| -| `EM-OBSERVE` | Request proceeds; `bundle_stale` annotation in telemetry | -| `EM-GUARD` | Request proceeds; `bundle_stale` annotation in telemetry | +| `EM-OBSERVE` | Request proceeds; `staleness.bundle_stale` annotation in telemetry | +| `EM-GUARD` | Request proceeds; `staleness.bundle_stale` annotation in telemetry | | `EM-STRICT` | Request denied with `BUNDLE_STALE` error code | Configure staleness via: @@ -172,12 +172,12 @@ In EM-STRICT, unknown obligation types cause the request to be denied. ### Step 4: Configure Break-Glass (Optional) -For emergency access when the PDP is down or mis-configured: +For emergency access when the PDP is down or misconfigured: 1. Generate a dedicated Ed25519 keypair for break-glass tokens: ```bash - capiscio keygen --output breakglass-key + capiscio key gen --out-priv breakglass-key.pem --out-pub breakglass-key.pub.pem ``` 2. Configure the server with the public key path: @@ -215,7 +215,7 @@ All PDP-related environment variables: The PEP sends a JSON POST to the PDP endpoint. Your PDP must accept this format: -```json +```jsonc { "pip_version": "capiscio.pip.v1", "subject": { @@ -225,11 +225,11 @@ The PEP sends a JSON POST to the PDP endpoint. Your PDP must accept this format: "trust_level": "DV" // Trust level code (DV/OV/EV) — maps to IAL 1/2/3 }, "action": { - "operation": "POST /api/v1/badges", + "operation": "POST /v1/badges", "capability_class": null }, "resource": { - "identifier": "/api/v1/badges" + "identifier": "/v1/badges" }, "context": { "txn_id": "txn-uuid", @@ -263,7 +263,7 @@ Confirm policy enforcement is active: ```bash # Send a request and check response headers -curl -v https://your-server/api/v1/badges \ +curl -v https://your-server/v1/badges \ -H "X-Capiscio-Badge: $BADGE_JWS" # Look for Server-Timing header with policy timing From fcb8cfda1e5e5049e26b1933b22a9d56327705de Mon Sep 17 00:00:00 2001 From: Beon de Nood Date: Sat, 21 Mar 2026 03:33:20 -0400 Subject: [PATCH 5/6] fix: address Copilot review round 4 comments - Use YOUR_WORKSPACE_UUID instead of angle-bracket placeholder - Use /v1/agents as verified endpoint in curl example --- docs/how-to/security/policy-enforcement.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/how-to/security/policy-enforcement.md b/docs/how-to/security/policy-enforcement.md index f1576df..6688b4d 100644 --- a/docs/how-to/security/policy-enforcement.md +++ b/docs/how-to/security/policy-enforcement.md @@ -44,7 +44,7 @@ The embedded PDP runs an OPA evaluator inside the server process. It automatical ```bash export CAPISCIO_EMBEDDED_PDP=true -export CAPISCIO_WORKSPACE= +export CAPISCIO_WORKSPACE=YOUR_WORKSPACE_UUID export CAPISCIO_ENFORCEMENT_MODE=EM-OBSERVE ``` @@ -263,7 +263,7 @@ Confirm policy enforcement is active: ```bash # Send a request and check response headers -curl -v https://your-server/v1/badges \ +curl -v https://your-server/v1/agents \ -H "X-Capiscio-Badge: $BADGE_JWS" # Look for Server-Timing header with policy timing From 0a7964b4d34db783fb81e5152d44d7ce8a46f2f6 Mon Sep 17 00:00:00 2001 From: Beon de Nood Date: Sat, 21 Mar 2026 03:41:34 -0400 Subject: [PATCH 6/6] docs: clarify trust_level format in PDP request example --- docs/how-to/security/policy-enforcement.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/how-to/security/policy-enforcement.md b/docs/how-to/security/policy-enforcement.md index 6688b4d..e68f217 100644 --- a/docs/how-to/security/policy-enforcement.md +++ b/docs/how-to/security/policy-enforcement.md @@ -222,7 +222,7 @@ The PEP sends a JSON POST to the PDP endpoint. Your PDP must accept this format: "did": "did:web:agent.example.com", "badge_jti": "badge-uuid", "ial": "1", - "trust_level": "DV" // Trust level code (DV/OV/EV) — maps to IAL 1/2/3 + "trust_level": "DV" // Badge trust level code: SS(0), REG(1), DV(2), OV(3), EV(4) }, "action": { "operation": "POST /v1/badges",