From 653cf4f68f6cf8c78ea87b2dc0ffc8a668ba63f7 Mon Sep 17 00:00:00 2001 From: Beon de Nood Date: Fri, 8 May 2026 00:52:51 -0400 Subject: [PATCH] docs: RFC-008 delegation chains, envelope APIs, and May 2026 updates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New pages: - concepts/delegation.md: Authority Envelopes & Delegation Chains concept - how-to/security/delegation-chains.md: Step-by-step delegation chain guide Updated references: - reference/grpc.md: Add 4 envelope RPCs (CreateEnvelope, DeriveEnvelope, BuildTransportHeaders, VerifyEnvelopeChain) and SimpleGuardService to service overview - reference/sdk-python/simple-guard.md: Add create_envelope(), derive_envelope(), make_delegation_headers() docs - reference/sdk-python/mcp.md: Add structured rejection fields (error_code, rejection_detail, requested/presented_capability) and scope_insufficient deny reason (RFC-008 B8) - reference/server/policy-enforcement.md: Add EnvelopeVerification config (CAPISCIO_MAX_CHAIN_DEPTH, CAPISCIO_ORG_TRUST_BOUNDARY), chain headers, chain verification error codes, PDP enrichment fields Updated guides: - identity/index.md: Show connect() zero-argument env var fallback - how-to/security/gateway-setup.md: Add chain verification config section - how-to/security/badge-keeper.md: Document automatic CA→PoP mode upgrade - mcp-guard/guides/evidence.md: Add GuardEventEmitter and policy_enforced event emission on deny Nav: - Add 'Delegation Chains' to Concepts and How-To > Security sections --- docs/concepts/delegation.md | 161 ++++++++++++++ docs/how-to/security/badge-keeper.md | 19 ++ docs/how-to/security/delegation-chains.md | 232 ++++++++++++++++++++ docs/how-to/security/gateway-setup.md | 20 ++ docs/identity/index.md | 16 +- docs/mcp-guard/guides/evidence.md | 53 +++++ docs/reference/grpc.md | 134 +++++++++++ docs/reference/sdk-python/mcp.md | 8 + docs/reference/sdk-python/simple-guard.md | 100 +++++++++ docs/reference/server/policy-enforcement.md | 49 ++++- mkdocs.yml | 2 + 11 files changed, 790 insertions(+), 4 deletions(-) create mode 100644 docs/concepts/delegation.md create mode 100644 docs/how-to/security/delegation-chains.md diff --git a/docs/concepts/delegation.md b/docs/concepts/delegation.md new file mode 100644 index 0000000..eab7333 --- /dev/null +++ b/docs/concepts/delegation.md @@ -0,0 +1,161 @@ +--- +title: Delegation Chains +description: How authority envelopes enable scoped, verifiable delegation between agents +--- + +# Delegation Chains + +In multi-agent workflows, one agent often needs to act on behalf of another. Authority Envelopes provide a cryptographic mechanism for scoped, verifiable delegation. + +--- + +## The Problem + +Consider a workflow where Agent A asks Agent B to perform a database query, and Agent B delegates that to Agent C (a specialized database reader). Without delegation: + +- Agent C has no proof that Agent A authorized this action +- There's no way to scope what Agent C is allowed to do +- The chain of authority is invisible to enforcement points + +Trust badges prove **identity** ("I am Agent B"), but not **authority** ("Agent A authorized me to read from the database"). + +--- + +## Authority Envelopes + +An **Authority Envelope** is a JWS-signed token (defined in [RFC-008](https://github.com/capiscio/capiscio-rfcs/blob/main/docs/008-delegated-authority-envelopes.md)) that grants scoped authority from one agent to another. + +Key claims in an envelope: + +| Claim | Description | +|-------|-------------| +| `iss` | DID of the agent granting authority | +| `sub` | DID of the agent receiving authority | +| `cap` | Capability class (e.g., `tools.database.read`) | +| `depth` | Remaining delegation depth (decrements at each hop) | +| `exp` | Expiration time | +| `parent_hash` | SHA-256 hash of the parent envelope (for chain integrity) | +| `constraints` | Optional restrictions (time windows, resource filters) | +| `enforcement_mode_min` | Minimum enforcement mode for this delegation | + +--- + +## Chain Structure + +Envelopes form **hash-linked chains** where each child references its parent: + +``` +┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ +│ Root Envelope │────▶│ Child Envelope │────▶│ Leaf Envelope │ +│ │ │ │ │ │ +│ iss: Agent A │ │ iss: Agent B │ │ iss: Agent C │ +│ sub: Agent B │ │ sub: Agent C │ │ sub: Agent D │ +│ cap: tools.* │ │ cap: tools.db.* │ │ cap: tools.db.rd │ +│ depth: 2 │ │ depth: 1 │ │ depth: 0 │ +└──────────────────┘ └──────────────────┘ └──────────────────┘ +``` + +### Monotonic Narrowing + +Each link in the chain must be **equal or narrower** than its parent: + +- **Capability class** can only narrow (e.g., `tools.*` → `tools.db.*` → `tools.db.read`) +- **Delegation depth** must decrease +- **Constraints** can only become more restrictive +- **Expiration** cannot exceed the parent's expiration + +This ensures authority can never be escalated through delegation. + +### Enforcement Mode Escalation + +Envelopes can set a minimum enforcement mode via `enforcement_mode_min`. This mode can only **escalate** (become stricter) through the chain: + +| Mode | Strictness | Behavior on DENY | +|------|-----------|-------------------| +| `EM-OBSERVE` | Lowest | Logged, request proceeds | +| `EM-GUARD` | Medium | Request blocked | +| `EM-STRICT` | Highest | Request blocked, unknown obligations denied | + +If a parent sets `EM-GUARD`, no child can relax it to `EM-OBSERVE`. + +--- + +## Chain Verification + +When the gateway PEP receives a request with delegation headers, it verifies the entire chain (RFC-008 §9.2): + +1. **Signature verification** — Each envelope is signed by its issuer +2. **Hash chain integrity** — Each child's `parent_hash` matches the parent +3. **Narrowing validation** — Capabilities, depth, and constraints narrow monotonically +4. **Badge binding** — The leaf envelope's `sub` matches the caller's badge subject +5. **Depth limits** — Chain length does not exceed `MaxChainDepth` (default: 10) +6. **Expiration** — All envelopes in the chain are within their validity period +7. **Enforcement mode** — The strictest mode across the chain is applied + +--- + +## Trust Boundaries + +Delegation chains can span organizational boundaries. The `OrgTrustBoundary` configuration controls whether cross-org chains are accepted: + +- **Same-org chains**: All issuers share the same org DID prefix — always accepted +- **Cross-org chains**: Issuers from different orgs — accepted only when `OrgTrustBoundary` is empty or matches + +--- + +## Relationship to Badges + +Envelopes and badges serve complementary roles: + +| | Badge | Envelope | +|-|-------|----------| +| **Proves** | Identity ("I am Agent B") | Authority ("Agent A authorized me") | +| **Issued by** | CA / Self-signed | Another agent | +| **Scope** | Agent-level trust | Per-action capability | +| **Lifetime** | Hours to days | Minutes to hours | +| **Header** | `X-Capiscio-Badge` | `X-Capiscio-Authority` | + +A delegated request carries both: the badge proves who the caller is, and the envelope chain proves they have authority to act. + +--- + +## Transport Headers + +Delegation chains are transmitted via HTTP headers: + +| Header | Content | +|--------|---------| +| `X-Capiscio-Authority` | Leaf envelope JWS | +| `X-Capiscio-Authority-Chain` | Base64url-encoded JSON array of the full chain | +| `X-Capiscio-Badge-Map` | JSON object mapping intermediate agent DIDs to their badge JWS tokens | + +--- + +## DID Resolution + +Chain verification requires resolving issuer DIDs to their public keys. CapiscIO uses a **composite key resolver** that handles both DID methods: + +| DID Method | Resolution | Example | +|------------|-----------|---------| +| `did:key` | Local decode (no network) | `did:key:z6Mk...` | +| `did:web` | HTTPS fetch of DID document | `did:web:agent.example.com` | + +### DID:web Security + +The `did:web` resolver includes SSRF protections (RFC-008 §17.1): + +- **HTTPS required** in production (HTTP only allowed in dev mode) +- **Blocked destinations**: localhost, private IPs (10.x, 172.16.x, 192.168.x), link-local, cloud metadata endpoints +- **No redirect following** (prevents SSRF via redirect chains) +- **Response size limits**: 64 KB maximum +- **Request timeouts**: 10 seconds +- **Document caching**: 5-minute TTL (reduces network calls) + +--- + +## Next Steps + +- [Create and use delegation chains](../how-to/security/delegation-chains.md) — Step-by-step guide +- [Gateway setup](../how-to/security/gateway-setup.md) — Configure the PEP to verify chains +- [Policy enforcement config](../reference/server/policy-enforcement.md) — Chain verification settings +- [RFC-008](https://github.com/capiscio/capiscio-rfcs/blob/main/docs/008-delegated-authority-envelopes.md) — Full specification diff --git a/docs/how-to/security/badge-keeper.md b/docs/how-to/security/badge-keeper.md index c4d6fee..1769cd2 100644 --- a/docs/how-to/security/badge-keeper.md +++ b/docs/how-to/security/badge-keeper.md @@ -280,6 +280,25 @@ Reduce check frequency: --- +## Automatic PoP Mode Upgrade + +When badge keeper starts in CA mode, it automatically upgrades to **Proof of Possession (PoP) mode** if the agent's private key is available: + +1. Loads the agent's private JWK from `~/.capiscio/keys/` +2. Derives the agent's DID via `did:key` +3. Uses the PoP endpoints (`/v1/sdk/agents/{did}/badge/challenge` → `/pop`) + +This upgrade is transparent — no configuration change is needed. PoP badges (IAL-1) provide stronger identity assurance than CA-only badges because they cryptographically prove the agent possesses the private key corresponding to its DID. + +!!! info "When does PoP upgrade happen?" + The upgrade occurs automatically when: + + - The agent's private key exists at `~/.capiscio/keys/` + - The badge keeper is configured with `--key` pointing to the key + - The CapiscIO Registry supports PoP (all current versions do) + +--- + ## See Also - [Issue and Verify Badges](./badges.md) - Manual badge workflow diff --git a/docs/how-to/security/delegation-chains.md b/docs/how-to/security/delegation-chains.md new file mode 100644 index 0000000..a6b8b8d --- /dev/null +++ b/docs/how-to/security/delegation-chains.md @@ -0,0 +1,232 @@ +--- +title: Delegation Chains +description: Create and verify authority delegation chains between agents +--- + +# Create and Verify Delegation Chains + +Use Authority Envelopes to delegate scoped authority from one agent to another in multi-agent workflows. + +--- + +## Prerequisites + +- CapiscIO SDK installed (`pip install capiscio-sdk`) +- Agent registered with a trust badge +- `SimpleGuard` initialized with agent keys + +```python +from capiscio_sdk import CapiscIO + +agent = CapiscIO.connect() # Reads CAPISCIO_API_KEY from env +guard = agent.simple_guard() +``` + +--- + +## Step 1: Create a Root Envelope + +The root envelope is the starting point of a delegation chain. It grants authority from your agent to another agent. + +```python +# Agent A delegates "tools.database.read" to Agent B +envelope = guard.create_envelope( + subject_did="did:web:agent-b.example.com", + capability_class="tools.database.read", + delegation_depth_remaining=1, # Agent B can delegate once more + expires_in_seconds=3600, # 1 hour +) +``` + +### With Constraints + +Add constraints to further limit what the delegate can do: + +```python +envelope = guard.create_envelope( + subject_did="did:web:agent-b.example.com", + capability_class="tools.database.read", + delegation_depth_remaining=1, + constraints={ + "tables": ["users", "orders"], + "max_rows": 1000, + }, +) +``` + +### With Enforcement Mode + +Set a minimum enforcement mode for the chain: + +```python +envelope = guard.create_envelope( + subject_did="did:web:agent-b.example.com", + capability_class="tools.database.read", + delegation_depth_remaining=1, + enforcement_mode_min="EM-GUARD", # Cannot be relaxed downstream +) +``` + +--- + +## Step 2: Derive a Child Envelope + +When Agent B needs to further delegate to Agent C, it derives a child envelope. The child must be equal or narrower than the parent. + +```python +# Agent B narrows the capability and passes to Agent C +child = guard.derive_envelope( + parent_envelope_jws=envelope, + subject_did="did:web:agent-c.example.com", + capability_class="tools.database.read", # Same or narrower + delegation_depth_remaining=0, # No further delegation + expires_in_seconds=1800, # Shorter than parent + constraints={ + "tables": ["users"], # Narrower: only "users" table + "max_rows": 100, # More restrictive + }, +) +``` + +!!! warning "Narrowing Rules" + - `capability_class` must be equal or narrower than the parent's + - `delegation_depth_remaining` must be less than the parent's + - `constraints` can only become more restrictive + - `expires_in_seconds` cannot exceed the parent's remaining validity + - `enforcement_mode_min` can only escalate (become stricter) + + Violations raise `ConfigurationError`. + +--- + +## Step 3: Send a Delegated Request + +Build HTTP headers that include the full chain and send them with your request: + +```python +import httpx + +# Build headers with the complete delegation chain +headers = guard.make_delegation_headers( + chain=[envelope, child], # Ordered: [root, ..., leaf] + badge_map={ + # Map intermediate agent DIDs to their badge JWS + "did:web:agent-b.example.com": agent_b_badge_jws, + }, +) + +# Send the request — the gateway verifies the entire chain +response = httpx.post( + "https://api.example.com/data", + headers=headers, + json={"query": "SELECT name FROM users LIMIT 10"}, +) +``` + +The `make_delegation_headers()` method produces these headers: + +| Header | Content | +|--------|---------| +| `X-Capiscio-Badge` | Your agent's badge JWS | +| `X-Capiscio-Authority` | Leaf envelope JWS | +| `X-Capiscio-Authority-Chain` | Base64url-encoded JSON array of the full chain | +| `X-Capiscio-Badge-Map` | JSON mapping of intermediate DIDs to badge JWS tokens | + +--- + +## Step 4: Server-Side Verification + +The CapiscIO gateway PEP automatically verifies delegation chains. No code changes are needed — just configure the gateway. + +### Gateway Configuration + +```bash +# Enable chain verification (on by default when gateway PEP is active) +export CAPISCIO_MAX_CHAIN_DEPTH=10 # Maximum chain depth (default: 10) +export CAPISCIO_ORG_TRUST_BOUNDARY="" # Empty = accept cross-org chains +``` + +### What Gets Verified + +The PEP performs these checks on every request with delegation headers: + +1. Signature validity of each envelope in the chain +2. Hash chain integrity (each child references its parent) +3. Monotonic narrowing of capabilities, depth, and constraints +4. Leaf subject matches the caller's badge DID +5. Chain length within `MaxChainDepth` +6. All envelopes are within their validity period +7. Enforcement mode escalation is applied + +### Error Responses + +| Error Code | HTTP Status | Description | +|-----------|-------------|-------------| +| `ENVELOPE_CHAIN_TOO_DEEP` | 403 | Chain exceeds `MaxChainDepth` | +| `ENVELOPE_SIGNATURE_INVALID` | 401 | Envelope signature verification failed | +| `ENVELOPE_NARROWING_VIOLATION` | 403 | Child is wider than parent | +| `ENVELOPE_EXPIRED` | 401 | Envelope has expired | +| `ENVELOPE_DEPTH_EXCEEDED` | 403 | Delegation depth remaining is negative | + +In `EM-OBSERVE` mode, chain verification failures are logged but the request proceeds. + +--- + +## Full Example + +A complete three-agent delegation: + +```python +from capiscio_sdk import CapiscIO +import httpx + +# Agent A: the orchestrator +agent_a = CapiscIO.connect(api_key="sk_live_aaa") +guard_a = agent_a.simple_guard() + +# Create root envelope: A → B +root = guard_a.create_envelope( + subject_did="did:web:agent-b.example.com", + capability_class="tools.database.*", + delegation_depth_remaining=2, +) + +# Agent B: the middleware +agent_b = CapiscIO.connect(api_key="sk_live_bbb") +guard_b = agent_b.simple_guard() + +# Derive child: B → C (narrowed to read-only) +child = guard_b.derive_envelope( + parent_envelope_jws=root, + subject_did="did:web:agent-c.example.com", + capability_class="tools.database.read", + delegation_depth_remaining=1, +) + +# Agent C: the leaf executor +agent_c = CapiscIO.connect(api_key="sk_live_ccc") +guard_c = agent_c.simple_guard() + +# C sends the delegated request +headers = guard_c.make_delegation_headers( + chain=[root, child], + badge_map={ + "did:web:agent-b.example.com": agent_b.get_badge(), + }, +) + +response = httpx.post( + "https://api.example.com/data", + headers=headers, + json={"query": "SELECT name FROM users"}, +) +``` + +--- + +## See Also + +- [Delegation Chains concept](../../concepts/delegation.md) — What are delegation chains and why they matter +- [Gateway setup](gateway-setup.md) — Deploy the CapiscIO security gateway +- [Policy enforcement config](../../reference/server/policy-enforcement.md) — Chain verification settings +- [RFC-008: Delegated Authority Envelopes](https://github.com/capiscio/capiscio-rfcs/blob/main/docs/008-delegated-authority-envelopes.md) — Full specification diff --git a/docs/how-to/security/gateway-setup.md b/docs/how-to/security/gateway-setup.md index eb2dba8..1821cb0 100644 --- a/docs/how-to/security/gateway-setup.md +++ b/docs/how-to/security/gateway-setup.md @@ -362,8 +362,28 @@ Your agent isn't reachable: --- +## Authority Chain Verification + +The gateway automatically verifies [delegation chains](delegation-chains.md) when requests include `X-Capiscio-Authority` headers. No additional setup is required — chain verification is enabled by default. + +### Configuration + +```bash +# Optional: adjust max chain depth (default: 10) +export CAPISCIO_MAX_CHAIN_DEPTH=5 + +# Optional: restrict to same-org chains +export CAPISCIO_ORG_TRUST_BOUNDARY="did:web:myorg.example.com" +``` + +When a valid chain is present, the gateway enriches the PDP request with the leaf capability and chain depth, allowing policies to make fine-grained decisions based on delegated authority. + +--- + ## See Also - [Issue and Verify Badges](./badges.md) - Create badges for testing - [Badge Keeper](./badge-keeper.md) - Auto-renew client badges +- [Delegation Chains](./delegation-chains.md) - Create and verify delegation chains +- [Policy Enforcement Config](../../reference/server/policy-enforcement.md) - Chain verification settings - [CLI Reference: gateway](../../reference/cli/index.md#gateway-start) - Full command reference diff --git a/docs/identity/index.md b/docs/identity/index.md index ee1931f..234981a 100644 --- a/docs/identity/index.md +++ b/docs/identity/index.md @@ -44,15 +44,25 @@ did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK ```python from capiscio_sdk import CapiscIO - agent = CapiscIO.connect(api_key="sk_live_...") + # Simplest: reads CAPISCIO_API_KEY from environment + agent = CapiscIO.connect() print(agent.did) # did:key:z6Mk... + + # Or pass explicitly + agent = CapiscIO.connect(api_key="sk_live_...") ``` === "Environment Variables" + ```bash + # connect() reads these automatically: + export CAPISCIO_API_KEY=sk_live_... # Required + export CAPISCIO_AGENT_ID=my-agent # Optional + export CAPISCIO_SERVER_URL=https://... # Optional (defaults to production) + ``` + ```python - # Set CAPISCIO_API_KEY in your environment - agent = CapiscIO.from_env() + agent = CapiscIO.connect() # Reads from env vars ``` This identity: diff --git a/docs/mcp-guard/guides/evidence.md b/docs/mcp-guard/guides/evidence.md index 4985fed..c0b609f 100644 --- a/docs/mcp-guard/guides/evidence.md +++ b/docs/mcp-guard/guides/evidence.md @@ -128,3 +128,56 @@ Evidence logging helps with: - **GDPR**: Parameters are hashed, not stored raw - **HIPAA**: Track who accessed what tools - **PCI DSS**: Monitor privileged operations + +--- + +## Event Emission on Deny + +When the `@guard` decorator denies a tool invocation, it automatically emits a `capiscio.policy_enforced` event to the CapiscIO Registry via the `GuardEventEmitter`. + +### Auto-Configuration + +If you use `MCPServerIdentity.connect()`, the event emitter is configured automatically — no manual setup required. + +### Manual Configuration + +```python +from capiscio_mcp import GuardEventEmitter, set_event_emitter + +emitter = GuardEventEmitter( + server_url="https://registry.capisc.io", + api_key="sk_live_...", + agent_id="my-agent", # Optional + enabled=True, # Disable with False + timeout=5.0, # HTTP timeout in seconds +) +set_event_emitter(emitter) +``` + +### Event Payload + +Events are POST'd to `/v1/events` with fire-and-forget semantics — failures do not block tool execution. + +| Field | Description | +|-------|-------------| +| `decision` | `"deny"` | +| `tool_name` | Tool that was denied | +| `deny_reason` | Reason code (e.g., `trust_insufficient`) | +| `deny_detail` | Human-readable explanation | +| `agent_did` | Caller's DID | +| `trust_level` | Caller's trust level | +| `evidence_id` | Evidence record ID | +| `error_code` | Structured error code (e.g., `SCOPE_INSUFFICIENT`) | +| `requested_capability` | Capability the caller asked for | +| `presented_capability` | Capability the caller presented | +| `severity` | Event severity level | + +### Accessing the Emitter + +```python +from capiscio_mcp import get_event_emitter + +emitter = get_event_emitter() +if emitter: + print(f"Event emission enabled: {emitter.enabled}") +``` diff --git a/docs/reference/grpc.md b/docs/reference/grpc.md index 6fd5be4..3ac5044 100644 --- a/docs/reference/grpc.md +++ b/docs/reference/grpc.md @@ -15,6 +15,7 @@ The gRPC API provides four core services: | `BadgeService` | Issue, verify, and manage trust badges | | `MCPService` | MCP tool access control and server identity (RFC-006/007) | | `ValidationService` | Schema validation for agent cards | +| `SimpleGuardService` | Authority envelope operations (RFC-008) | --- @@ -390,6 +391,137 @@ identity = client.mcp.parse_server_identity_jsonrpc( --- +## SimpleGuardService — Authority Envelopes + +Create and verify delegated authority chains per [RFC-008](https://github.com/capiscio/capiscio-rfcs/blob/main/docs/008-delegated-authority-envelopes.md). + +### CreateEnvelope + +Create a root authority envelope delegating authority to another agent. + +```protobuf +rpc CreateEnvelope(CreateEnvelopeRequest) returns (CreateEnvelopeResponse); +``` + +**Request:** + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `key_id` | `string` | Yes | Key ID for signing | +| `subject_did` | `string` | Yes | DID of the agent receiving authority | +| `capability_class` | `string` | Yes | Dot-notation capability (e.g., `tools.database.read`) | +| `delegation_depth_remaining` | `int32` | Yes | How many further delegations allowed | +| `issuer_badge_jti` | `string` | No | JTI of the issuer's badge | +| `txn_id` | `string` | No | Transaction ID (auto-generated if empty) | +| `expires_in_seconds` | `int64` | No | TTL from now (default: 3600) | +| `constraints_json` | `string` | No | JSON-serialized constraints object | +| `subject_badge_jti` | `string` | No | JTI of the subject's badge | +| `enforcement_mode_min` | `string` | No | Minimum enforcement mode | + +**Response:** + +| Field | Type | Description | +|-------|------|-------------| +| `envelope_jws` | `string` | JWS Compact Serialization of the signed envelope | +| `error_message` | `string` | Error description (empty on success) | + +### DeriveEnvelope + +Derive a child authority envelope from a parent, with hash linking and narrowing validation. + +```protobuf +rpc DeriveEnvelope(DeriveEnvelopeRequest) returns (DeriveEnvelopeResponse); +``` + +**Request:** + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `key_id` | `string` | Yes | Key ID for signing | +| `parent_envelope_jws` | `string` | Yes | Parent envelope JWS to derive from | +| `subject_did` | `string` | Yes | DID of the next delegate | +| `capability_class` | `string` | Yes | Must be equal or narrower than parent's | +| `delegation_depth_remaining` | `int32` | Yes | Must be less than parent's depth | +| `issuer_badge_jti` | `string` | No | JTI of the child issuer's own badge | +| `expires_in_seconds` | `int64` | No | TTL from now (default: 1800) | +| `constraints_json` | `string` | No | Must be equal or more restrictive than parent's | +| `subject_badge_jti` | `string` | No | JTI of the subject's badge | +| `enforcement_mode_min` | `string` | No | Cannot relax parent's mode | + +**Response:** + +| Field | Type | Description | +|-------|------|-------------| +| `envelope_jws` | `string` | JWS Compact Serialization of the signed child envelope | +| `error_message` | `string` | Error description (empty on success) | + +!!! warning "Narrowing Violations" + `DeriveEnvelope` returns an error if the child violates monotonic narrowing rules (capability, depth, constraints, or enforcement mode). + +### BuildTransportHeaders + +Encode a delegation chain into HTTP transport headers for use in requests. + +```protobuf +rpc BuildTransportHeaders(BuildTransportHeadersRequest) returns (BuildTransportHeadersResponse); +``` + +**Request:** + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `chain` | `string[]` | Yes | Ordered list of envelope JWS strings `[root, ..., leaf]` | +| `badge_map_json` | `string` | No | JSON object mapping DID → badge JWS for intermediate agents | + +**Response:** + +| Field | Type | Description | +|-------|------|-------------| +| `authority` | `string` | Value for `X-Capiscio-Authority` header (leaf JWS) | +| `authority_chain` | `string` | Value for `X-Capiscio-Authority-Chain` header (base64url JSON array) | +| `badge_map` | `string` | Value for `X-Capiscio-Badge-Map` header (JSON object) | +| `error_message` | `string` | Error description (empty on success) | + +### VerifyEnvelopeChain + +Verify the integrity of an authority envelope chain. + +```protobuf +rpc VerifyEnvelopeChain(VerifyEnvelopeChainRequest) returns (VerifyEnvelopeChainResponse); +``` + +**Request:** + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `chain` | `string[]` | Yes | Ordered list of envelope JWS strings to verify | +| `badge_map_json` | `string` | No | JSON object mapping DID → badge JWS for badge-issuer lookups | +| `enforcement_mode` | `string` | No | Enforcement mode to apply during verification | + +**Response:** + +| Field | Type | Description | +|-------|------|-------------| +| `valid` | `bool` | Whether the chain is valid | +| `leaf_capability` | `string` | Capability class of the leaf envelope | +| `leaf_subject` | `string` | Subject DID of the leaf envelope | +| `chain_depth` | `int32` | Number of envelopes in the chain | +| `error_message` | `string` | Error description (empty when valid) | +| `error_code` | `string` | Machine-readable error code | + +**Error Codes:** + +| Code | Description | +|------|-------------| +| `ENVELOPE_SIGNATURE_INVALID` | Envelope signature verification failed | +| `ENVELOPE_EXPIRED` | Envelope has expired | +| `ENVELOPE_CHAIN_BROKEN` | Hash chain integrity failure | +| `ENVELOPE_NARROWING_VIOLATION` | Child is wider than parent | +| `ENVELOPE_DEPTH_EXCEEDED` | Delegation depth remaining is negative | +| `ENVELOPE_CHAIN_TOO_DEEP` | Chain exceeds maximum allowed depth | + +--- + ## Trust Levels The `TrustLevel` enum maps to badge trust levels (RFC-002 §5): @@ -414,6 +546,7 @@ capiscio-core/proto/capiscio/v1/ ├── badge.proto # BadgeService ├── mcp.proto # MCPService (RFC-006/007) ├── scoring.proto # ScoringService +├── simpleguard.proto # SimpleGuardService (RFC-008) ├── common.proto # Shared types ├── did.proto # DID operations ├── registry.proto # Registry operations @@ -474,3 +607,4 @@ except RpcError as e: - [RFC-002: Trust Badge](https://github.com/capiscio/capiscio-rfcs/blob/main/docs/002-trust-badge.md) — Badge specification - [RFC-006: MCP Tool Authority](https://github.com/capiscio/capiscio-rfcs/blob/main/docs/006-mcp-tool-authority-evidence.md) — Tool access spec - [RFC-007: MCP Server Identity](https://github.com/capiscio/capiscio-rfcs/blob/main/docs/007-mcp-server-identity-discovery.md) — Server identity spec +- [RFC-008: Delegated Authority Envelopes](https://github.com/capiscio/capiscio-rfcs/blob/main/docs/008-delegated-authority-envelopes.md) — Delegation chain spec diff --git a/docs/reference/sdk-python/mcp.md b/docs/reference/sdk-python/mcp.md index fb0ae24..f6c3dd8 100644 --- a/docs/reference/sdk-python/mcp.md +++ b/docs/reference/sdk-python/mcp.md @@ -107,6 +107,13 @@ def evaluate_tool_access( | `evidence_json` | `str` | RFC-006 §7 evidence record | | `evidence_id` | `str` | Unique evidence record ID | | `timestamp` | `str` | Evaluation timestamp (ISO 8601) | +| `error_code` | `str` | Machine-readable error code (e.g., `SCOPE_INSUFFICIENT`) | +| `rejection_detail` | `str` | Structured rejection explanation | +| `requested_capability` | `str` | Capability the caller asked for | +| `presented_capability` | `str` | Capability the caller actually presented | + +!!! info "Structured Rejection Fields (RFC-008 B8)" + The `error_code`, `rejection_detail`, `requested_capability`, and `presented_capability` fields are populated when a denial involves capability scope violations. They are empty for other denial reasons (e.g., `badge_missing`). **Deny Reasons:** @@ -120,6 +127,7 @@ def evaluate_tool_access( | `tool_not_allowed` | Tool not in allowed list | | `issuer_untrusted` | Badge issuer not trusted | | `policy_denied` | Policy explicitly denied access | +| `scope_insufficient` | Presented capability does not cover the requested scope (RFC-008) | **Example:** diff --git a/docs/reference/sdk-python/simple-guard.md b/docs/reference/sdk-python/simple-guard.md index 623f5fa..d6807af 100644 --- a/docs/reference/sdk-python/simple-guard.md +++ b/docs/reference/sdk-python/simple-guard.md @@ -66,3 +66,103 @@ In `dev_mode=True`, all files are auto-generated if missing. separate_signature: true docstring_style: google docstring_section_style: spacy + +--- + +## Authority Envelopes (RFC-008) + +SimpleGuard provides methods for creating and verifying delegated authority chains. See the [Delegation Chains concept](../../concepts/delegation.md) for background. + +### `create_envelope()` + +Create a root Authority Envelope delegating authority to another agent. + +```python +envelope_jws = guard.create_envelope( + subject_did="did:web:worker.example.com", + capability_class="tools.database.read", + delegation_depth_remaining=1, + expires_in_seconds=3600, +) +``` + +**Parameters:** + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `subject_did` | `str` | *(required)* | DID of the agent receiving delegated authority | +| `capability_class` | `str` | *(required)* | Dot-notation capability (e.g., `tools.database.read`) | +| `delegation_depth_remaining` | `int` | `1` | How many further delegations allowed | +| `issuer_badge_jti` | `str` | `""` | JTI of the issuer's badge | +| `txn_id` | `str` | `""` | Transaction ID (auto-generated if empty) | +| `expires_in_seconds` | `int` | `3600` | TTL from now | +| `constraints` | `dict | None` | `None` | JSON-serializable constraints | +| `subject_badge_jti` | `str` | `""` | JTI of the subject's badge | +| `enforcement_mode_min` | `str` | `""` | Minimum enforcement mode | + +**Returns:** `str` — JWS Compact Serialization of the signed envelope. + +**Raises:** `ConfigurationError` — If signing fails or key is not available. + +### `derive_envelope()` + +Derive a child Authority Envelope from a parent, with hash linking and monotonic narrowing validation. + +```python +child_jws = guard.derive_envelope( + parent_envelope_jws=root_envelope, + subject_did="did:web:reader.example.com", + capability_class="tools.database.read", + delegation_depth_remaining=0, + constraints={"tables": ["users"]}, +) +``` + +**Parameters:** + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `parent_envelope_jws` | `str` | *(required)* | Parent envelope JWS to derive from | +| `subject_did` | `str` | *(required)* | DID of the next delegate | +| `capability_class` | `str` | *(required)* | Must be equal or narrower than parent's | +| `delegation_depth_remaining` | `int` | `0` | Must be less than parent's depth | +| `issuer_badge_jti` | `str` | `""` | JTI of the child issuer's own badge | +| `expires_in_seconds` | `int` | `1800` | TTL from now | +| `constraints` | `dict | None` | `None` | Must be equal or more restrictive than parent's | +| `subject_badge_jti` | `str` | `""` | JTI of the subject's badge | +| `enforcement_mode_min` | `str` | `""` | Cannot relax parent's mode | + +**Returns:** `str` — JWS Compact Serialization of the signed child envelope. + +**Raises:** `ConfigurationError` — On narrowing violation, depth exceeded, or signing failure. + +### `make_delegation_headers()` + +Generate HTTP headers for a delegated request combining the delegation chain with the agent's badge. + +```python +headers = guard.make_delegation_headers( + chain=[root_envelope, child_envelope], + badge_map={"did:web:worker.example.com": worker_badge_jws}, +) +# headers contains: X-Capiscio-Badge, X-Capiscio-Authority, +# X-Capiscio-Authority-Chain, X-Capiscio-Badge-Map +``` + +**Parameters:** + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `chain` | `list[str]` | *(required)* | Ordered list of envelope JWS strings `[root, ..., leaf]` | +| `badge_map` | `dict | None` | `None` | DID → badge JWS for intermediate agents in the chain | +| `payload` | `dict | None` | `None` | Optional payload for badge signing | +| `body` | `bytes | None` | `None` | Optional HTTP body bytes to bind to badge signature | + +**Returns:** `dict[str, str]` — Dictionary with these headers: + +| Header | Description | +|--------|-------------| +| `X-Capiscio-Badge` | This agent's badge JWS | +| `X-Capiscio-Authority` | Leaf envelope JWS | +| `X-Capiscio-Authority-Chain` | Base64url-encoded JSON array of the full chain | +| `X-Capiscio-Badge-Map` | JSON mapping of intermediate DIDs to badge JWS tokens | diff --git a/docs/reference/server/policy-enforcement.md b/docs/reference/server/policy-enforcement.md index aa40f3d..0bb9d37 100644 --- a/docs/reference/server/policy-enforcement.md +++ b/docs/reference/server/policy-enforcement.md @@ -18,6 +18,13 @@ Configuration reference for CapiscIO's Policy Enforcement Point (PEP) and PDP in | `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`) | +### Envelope Verification (RFC-008) + +| Variable | Default | Description | +|----------|---------|-------------| +| `CAPISCIO_MAX_CHAIN_DEPTH` | `10` | Maximum delegation chain depth (RFC-008 §9.5) | +| `CAPISCIO_ORG_TRUST_BOUNDARY` | _(empty)_ | Org DID prefix for cross-org chain restrictions. Empty = accept all. | + --- ## Enforcement Modes @@ -53,7 +60,9 @@ The PEP sends a JSON POST to the PDP endpoint. Your PDP must accept this format: }, "context": { "txn_id": "txn-uuid", - "enforcement_mode": "EM-GUARD" + "enforcement_mode": "EM-GUARD", + "leaf_capability": "tools.database.read", // From envelope chain (if present) + "delegation_depth": 2 // Chain length (if present) }, "environment": { "workspace": "00000000-0000-0000-0000-000000000000", @@ -103,8 +112,46 @@ Requires `X-Capiscio-Registry-Key` authentication. The API key must belong to th --- +## Authority Chain Headers + +When a request includes delegation chain headers, the PEP verifies the chain before querying the PDP. See [Delegation Chains](../../concepts/delegation.md) for background. + +### Request Headers + +| Header | Description | +|--------|-------------| +| `X-Capiscio-Authority` | Leaf authority envelope JWS | +| `X-Capiscio-Authority-Chain` | Base64url-encoded JSON array of the full envelope chain | +| `X-Capiscio-Badge-Map` | JSON object mapping intermediate agent DIDs to their badge JWS tokens | + +### Chain Verification Errors + +| Error Code | HTTP Status | Description | +|-----------|-------------|-------------| +| `ENVELOPE_CHAIN_TOO_DEEP` | 403 | Chain exceeds `CAPISCIO_MAX_CHAIN_DEPTH` | +| `ENVELOPE_SIGNATURE_INVALID` | 401 | Envelope signature verification failed | +| `ENVELOPE_NARROWING_VIOLATION` | 403 | Child is wider than parent | +| `ENVELOPE_EXPIRED` | 401 | Envelope has expired | +| `ENVELOPE_DEPTH_EXCEEDED` | 403 | Delegation depth remaining is negative | +| `ENVELOPE_CHAIN_BROKEN` | 401 | Hash chain integrity failure | + +In `EM-OBSERVE` mode, chain verification failures are logged but the request proceeds. + +### PDP Enrichment + +When a valid chain is present, the PDP request `context` is enriched with: + +| Field | Description | +|-------|-------------| +| `leaf_capability` | Capability class from the leaf envelope | +| `delegation_depth` | Number of envelopes in the verified chain | +| `enforcement_mode` | May be escalated if any envelope sets `enforcement_mode_min` | + +--- + ## See Also - [Policy Enforcement Setup](../../how-to/security/policy-enforcement.md) — Step-by-step setup guide +- [Delegation Chains](../../how-to/security/delegation-chains.md) — Creating and using delegation chains - [Policy Config YAML](../policy-config-yaml.md) — Policy configuration format - [Policy API](../policy-api.md) — Policy management API diff --git a/mkdocs.yml b/mkdocs.yml index f4c815e..e7adfd6 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -201,6 +201,7 @@ nav: - concepts/scoring.md - Enforcement: concepts/enforcement.md - Policy Scoping: concepts/policy-scoping.md + - Delegation Chains: concepts/delegation.md - MCP Security: - Overview: concepts/mcp-security.md @@ -224,6 +225,7 @@ nav: - Dev Mode: how-to/security/dev-mode.md - Key Rotation: how-to/security/key-rotation.md - Trust Store: how-to/security/trust-store.md + - Delegation Chains: how-to/security/delegation-chains.md - MCP Security: - Protect MCP Tools: mcp-guard/guides/server-side.md - Verify MCP Servers: mcp-guard/guides/client-side.md