# Security Control Verification Matrix

| Control Area | Control | Owner | Verification Method | Frequency |
|---|---|---|---|---|
| Auth | OIDC + short-lived access tokens | Security | integration tests + pen-test | each release |
| AuthZ | Role/tenant policy enforcement | Backend | authz test suite + code review gates | each PR |
| Secrets | secret manager only, no hardcoded secrets | Security/Platform | secret scan + config review | each PR |
| Transport | TLS at edge, secure internal transport | Platform | config validation + periodic scan | monthly |
| Session security | admin token emergency deny-list | Security/Backend | revocation drill + auth middleware test | each release |
| Realtime channel isolation | Redis ACL for notification publish/subscribe | Security/Platform | ACL config review + negative subscribe test | each release |
| Runtime key handling | No persistent user private-key storage for allocation access | Security/Platform | API contract review + storage/log scan + negative endpoint test | each release |
| Runtime key handling | User-managed SSH public key path only for runtime access | Security/Backend | request/response contract checks + integration tests | each release |
| Terminal session auth | Single-use terminal token atomic consume (Redis GETDEL) — no replay | Backend/Security | replay attack test (reuse consumed token → 401) | each release |
| Terminal session binding | Session bound to {session_id, user_id, allocation_id, node_id, expires_at} — gateway enforces at open | Backend | binding invariant test + mismatch rejection test | each release |
| Terminal session concurrency | Single active terminal session per allocation enforced at open | Backend | concurrent open attempt test → second open rejected | each release |
| Terminal session TTL | Active session closed at `terminal.session_max_ttl_seconds` (policy key, default 4h) — not inherited from OIDC token TTL | Backend | TTL expiry test with short policy override | each release |
| Terminal task signing | `terminal.open` task signed with Ed25519 — node rejects unsigned or replayed task | Security/Node | signature bypass test + replay test → node rejected | each release |
| Terminal least-privilege boundary | Terminal path allows only user impersonation bootstrap; interactive shell runs as allocation user with no privileged lifecycle operations | Security/Backend | integration test verifies effective uid/gid; negative test forbids privileged terminal operations; sudoers policy review | each release |
| SKU lifecycle governance | SKU lifecycle changes (create/update/activate/deactivate) require approval policy and node-registration references only active catalog SKUs | Platform/Inventory | change-review evidence + negative registration test for invalid/inactive SKU reference + runbook mapping check | each release |
| Terminal release sequencing | Allocation release closes terminal first (`allocation_released` frame + ack/timeout) before `allocation.revoke_user` | Backend | release-during-active-terminal integration test | each release |
| Terminal session audit | Every terminal open/close recorded in `platform_audit_logs` with session_id, user_id, allocation_id, reason | Backend | audit log presence check in integration tests | each release |
| Payments | webhook raw-body signature verification before JSON parse + event-id idempotency | Payments | integration replay tests + signature bypass tests | each release |
| Billing | immutable ledger writes | Billing | ledger invariant tests | each release |
| Audit | privileged action logging | Platform | log schema checks + spot audit | weekly |
| Abuse | WAF/rate limits/bot controls | Platform | load+abuse tests | monthly |

## Release Blocking Controls
- Auth/AuthZ verification pass
- Admin token revocation drill pass
- Redis notification ACL isolation test pass
- Runtime key handling checks pass (no persistent private-key endpoint/data path)
- Secrets scan pass
- Webhook replay/idempotency tests pass
- Webhook signature bypass test (`AT-053`) pass
- Ledger integrity tests pass
- Terminal token replay rejection test pass
- Terminal session binding invariant test pass
- Terminal least-privilege boundary checks pass
- SKU lifecycle governance checks pass
- Terminal release sequencing test pass (terminal closed before revoke_user)

## Implementation Requirements

### Stripe Webhook Signature Verification
The Payments service MUST verify the Stripe webhook signature against the **raw request body bytes** before any JSON parsing or deserialization occurs.
- Read the raw body stream once and buffer it.
- Verify the `Stripe-Signature` header against the buffered bytes using the signing secret.
- Only proceed to JSON parse if verification passes.
- Reject any request that pre-parses JSON before signature check — this is a known class of signature bypass attack.
- Required test case: `AT-053` — reuse a previously valid `Stripe-Signature` header with a modified request body and confirm `400` rejection.

### Runtime Access Key Handling (Post-Cutover)
Runtime access MUST avoid persistent user private-key storage and re-download paths.
- Allowed model: user-managed public key registration and runtime access via terminal/SSH proxy controls.
- Disallowed model: API or database persistence of user private key material for later retrieval.
- Verification evidence for this control family:
  - threat baseline linkage: `doc/architecture/Threat_Model.md`
  - ops tracking: `doc/governance/Agent_Work_Queue.yaml` task `C-OPS-003`
