custom
code
sovereign-compute
pax-coder / docs /adr /0009-protected-execution-capability.md
SNAPKITTYWEST's picture
chore: push pax-coder from SNAPKITTYWEST GitHub
ef6eb55 verified
|
Raw
History Blame Contribute Delete
12.4 kB
# 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)