| # 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) | |