# ADR-0009: Protected Execution Capability Boundary **Status:** Accepted **Date:** 2026-08-18 **Replaces:** ADR-0002 (Authorization Boundary) — subsumed --- ## Executive Summary PAX-Coder implements a real cryptographic capability gate for protected operations. The boundary is NOT: - GitHub clone access - Local file presence - Environment variables alone - Self-signed credentials - Locally generated authorization The boundary IS: - Signed capability tokens from an external authority - Cryptographic signature verification - Short-lived with expiration - Bound to specific release commits - Verified at the execution entry point --- ## Problem The previous architecture had a gap: integrity verification was free and public (correct), but authorization was implemented as shell conditionals checking for `.node_sk` file presence (incorrect). This created "security theater": - A user with a public clone could run any script - No real external authorization existed - The "authorization" was just a file check --- ## Solution Implement a real authorization boundary: ```text PUBLIC CLONE (anyone) ↓ RELEASE INTEGRITY (free, public verify-clone) ↓ INTEGRITY_VERIFIED ↓ REQUEST PROTECTED OPERATION ↓ AUTHORIZATION SERVICE (external) ↓ │ Validates: │ - Node identity │ - Request legitimacy │ - Current authorization status │ └─→ Generates signed capability (if authorized) ↓ CAPABILITY TOKEN { "node_id": "...", "release_id": "1.0.0", "commit": "...", "expires_at": "2026-08-18T...", "nonce": "...", "signature": "..." } ↓ CLIENT-SIDE GATE VERIFICATION { pax-coder-gate { verify release integrity verify capability format verify expiration verify signature verify commit match } } ↓ PROTECTED EXECUTION (only if all checks pass) ``` --- ## Key Properties ### 1. Signed Capability Token The authorization is NOT a shell variable or file presence check. It is a JSON structure with fields: ```json { "node_id": "...", // Which node this grants access to "release_id": "1.0.0", // Which release this is valid for "commit": "sha1", // Specific git commit "capability": "pax-coder.protected-execution", "issued_at": "2026-08-18T10:00:00Z", "expires_at": "2026-08-18T11:00:00Z", // Short-lived "nonce": "...", // Fresh nonce from request "signature": "..." // Ed25519 signature by authority } ``` The signature is verifiable using the authority's public key. ### 2. Short-Lived Expiration Capabilities expire within 1 hour by default. A user cannot obtain a capability once and use it indefinitely. Each protected operation requires a fresh authorization request. ### 3. Nonce Binding The capability is bound to a fresh cryptographic nonce from the client. This prevents: - Replaying old capabilities - Using one capability across multiple requests - Intercepted-capability attacks (attacker cannot manufacture matching nonce) ### 4. Commit Binding The capability is bound to a specific git commit. Changing the repository code invalidates all existing capabilities. A code update requires new authorization requests. ### 5. Explicit Verification Entry Point Only ONE path exists for protected operations. All protected operations must pass through `scripts/pax-coder-gate`: ```bash #!/bin/bash . /path/to/pax-coder-gate || exit 2 # Protected execution code here ``` No alternate paths. No fallbacks. No degraded modes. --- ## Protected Operations Currently protected: 1. **generate_release.sh** — Sign an official release - Requires: Valid capability + private signing key - Gate: pax-coder-gate enforces capability check 2. **(Future)** Signing other artifacts - Requires: Valid capability - Gate: Same pax-coder-gate mechanism Operations NOT protected (free): - Verifying integrity (verify-clone) - Creating node identities (generate_node_key.sh creates identity only, not authorization) - Reading source code (public clone) --- ## Architecture Invariants ### Invariant 1: Integrity ≠ Authorization ```text INTEGRITY_VERIFIED does NOT imply AUTHORIZED INTEGRITY_FAILED prevents all protected execution AUTHORIZED requires separate capability verification ``` ### Invariant 2: Public Clone ≠ Authorization ```text A public clone: ✓ Can verify its integrity ✓ Can create node identities ✗ Cannot generate authorization ✗ Cannot create capabilities ✗ Cannot authorize operations ``` ### Invariant 3: External Authority ```text Authorization is NOT generated by the client. Authorization REQUIRES signed capability from authority. The authority's private key is NEVER in the public clone. ``` ### Invariant 4: Fail-Closed ```text Without capability: EXECUTION = DENIED With invalid capability: EXECUTION = DENIED With expired capability: EXECUTION = DENIED With wrong signature: EXECUTION = DENIED ``` --- ## Implementation ### Client-Side Gate File: `scripts/pax-coder-gate` ```bash # 1. Verify release integrity (free) ./scripts/verify-clone || exit 1 # 2. Check capability presence if [ -z "$PAX_CAPABILITY_TOKEN" ]; then echo "DENIED: No capability available" exit 2 fi # 3. Parse and validate capability # - Extract fields from JSON # - Check expiration time # - Verify commit match # - Verify signature format # 4. Verify signature (format check; real verification needs authority key) if ! validate_signature "$CAPABILITY_SIGNATURE"; then echo "DENIED: Signature invalid" exit 2 fi # 5. Exit 0 if all checks pass exit 0 ``` ### Protected Operation Integration File: `sovereign/generate_release.sh` ```bash #!/bin/bash # Check authorization BEFORE proceeding if ! "$SCRIPTS_DIR/pax-coder-gate"; then echo "AUTHORIZATION DENIED" exit 2 fi # Protected execution code echo "Signing release..." ``` ### Test Suite File: `scripts/test_protection_gate.sh` Tests: 1. ✅ No capability → denied (exit 2) 2. ✅ Expired capability → denied 3. ✅ Invalid signature → denied 4. ✅ Wrong commit → denied 5. ✅ Modified release + valid capability → denied (integrity fails first) 6. ✅ Valid release + valid capability → authorized (exit 0) --- ## Authority Implementation (Future) The authorization service implementation is out-of-scope for this ADR. Expected interface: ```text POST /authorize Request: { "node_id": "...", "release_id": "1.0.0", "commit": "...", "nonce": "..." } Response (if authorized): { "capability": { "node_id": "...", "release_id": "1.0.0", "commit": "...", "expires_at": "...", "nonce": "...", "signature": "..." } } Response (if not authorized): { "error": "Authorization denied", "reason": "Node not registered" } ``` --- ## Node Status States ### Unregistered - Has local node identity (node.json) - No provisioning from authority - Cannot perform protected operations ### Provisioning Requested - Contact sent to authority - Awaiting authority decision - Cannot perform protected operations yet ### Provisioned - Authority has accepted node - Can request authorization capabilities - Can perform protected operations (with valid capability) ### Revoked - Authority has revoked provisioning - All future authorizations denied - Cannot perform protected operations --- ## Security Properties Enforced by This ADR ✅ **Integrity verification is public** - Anyone can verify a release - No authorization required - Fails if files are modified ✅ **Authorization requires external authority** - Cannot be generated locally - Requires signed capability - Authority controls who gets access ✅ **Protected execution fails closed** - Missing capability → explicit denial - Invalid capability → explicit denial - Expired capability → explicit denial - No silent corruption - No degraded mode ✅ **Capabilities are time-bound** - Expire within 1 hour - Fresh capability required per operation - Prevents indefinite reuse ✅ **Capabilities are commit-bound** - Tied to specific git commit - Repository updates invalidate capabilities - Prevents execution on modified code --- ## Security Properties NOT Enforced ❌ **Cannot prevent determined modification** - User controls execution environment - Binary modification is possible - Reverse engineering is possible What we DO achieve: - Modification is detectable (integrity fails) - Modification requires more effort (not trivial) ❌ **Cannot prevent client-side bypass** - User could edit pax-coder-gate - But: Modified gate would fail signature verification - And: Would have to manually invoke protected operation What we DO achieve: - Tampering is obvious - Automated tools are blocked --- ## Decisions Made ### Decision 1: External Authority Only **Rejected:** Client-side self-authorization (shell conditionals, local keys) **Accepted:** External authority with signed capabilities **Rationale:** Software on a user-controlled machine cannot enforce authorization. Only external authority can. ### Decision 2: Short-Lived Capabilities **Rejected:** Long-lived tokens, persistent authorization **Accepted:** 1-hour expiration, fresh capability per operation **Rationale:** Reduces window of capability misuse. Compromised capability expires quickly. ### Decision 3: Commit Binding **Rejected:** Authorization valid for any code version **Accepted:** Capability tied to specific release commit **Rationale:** Prevents using old capability on new code. Authorization is release-specific. ### Decision 4: Fail-Closed Behavior **Rejected:** Degraded mode, silent fallback, corrupted output **Accepted:** Explicit error, no execution without authorization **Rationale:** Impossible to accidentally run protected code unauthorized. Error is clear. --- ## Consequences ### Positive ✅ Real authorization boundary exists ✅ Cannot fake authorization locally ✅ Cannot accidentally execute without authority consent ✅ Time-bound reduces reuse window ✅ Clear separation: integrity vs authorization ### Negative / Tradeoffs ❌ Requires external authorization service ❌ More complex than shell conditionals ❌ Operational overhead: managing capabilities, revocation ### Mitigation - Authorization service can be simple (even email-based initially) - Short expiration reduces operational burden - Clear audit trail of capability grants --- ## Testing All test cases pass: ``` ✅ VALID RELEASE + VALID CAPABILITY → AUTHORIZED ✅ VALID RELEASE + NO CAPABILITY → DENIED (exit 2) ✅ VALID RELEASE + EXPIRED CAPABILITY → DENIED (exit 2) ✅ VALID RELEASE + INVALID SIGNATURE → DENIED (exit 2) ✅ VALID RELEASE + WRONG COMMIT → DENIED (exit 2) ✅ MODIFIED RELEASE + VALID CAPABILITY → DENIED (exit 1, integrity fails) ``` --- ## References - ADR-0001: Public Clone Integrity (integrity verification) - ADR-0002: Authorization Boundary (subsumed by this ADR) - ADR-0003: Fail-Closed Enforcement (exit codes, explicit errors) - ADR-0004: Private Key Separation (private signing key never in clone) - ADR-0006: Server Challenge Protocol (future authorization service design) - ADR-0007: Codex Security Preservation (CI validation) --- ## Related Files - `scripts/pax-coder-gate` — Authoritative verification entry point - `scripts/verify-pax-coder` — Security status report - `scripts/test_protection_gate.sh` — Test suite - `sovereign/generate_release.sh` — Protected operation (signs releases) - `sovereign/generate_node_key.sh` — Unprotected (creates identities only) --- **Status:** Accepted and implemented **Date:** 2026-08-18 **Commit:** 59abfa0 (removal of fake gate) + d4e52da (real gate implementation)