railiance-platform/docs/ephemeral-custody-lifecycle.md
codex 30e6edc236
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Add versioned ephemeral custody lifecycle
Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a02669-87ee-7a31-b111-edc95a16e0fa
2026-08-22 21:56:42 +02:00

5.6 KiB

Ephemeral custody projection lifecycle

scripts/custody-projection.py is the platform-owned interface for future short-lived, mount-only credential exercises. It replaces copying the dated audit-core-whitehat-e2-credentials*.py, HCL, and YAML files. Those dated artifacts are retained only as historical evidence and cleanup references for their terminal engagement ids.

The lifecycle does not authorize an engagement. A projection contract is accepted only when it is approved, lasts at most 900 seconds, binds exact target and runner revisions, names exactly two distinct sender identities, and uses engagement- and target-bound KV paths. Kubernetes and OpenBao resource names are derived from the SHA-256 of the engagement id; no previous engagement name is copied into a new manifest.

Versioned interfaces

Interface Schema Producer Consumer
railiance.custody-projection-contract v1 schemas/custody-projection-contract.schema.json engagement/platform owners platform lifecycle and Whitehat review
railiance.custody-broker-readiness v1 schemas/custody-broker-readiness.schema.json whitehat-security platform pre-projection gate
railiance.custody-projection-receipt v1 schemas/custody-projection-receipt.schema.json platform lifecycle Whitehat broker adapter and operator
railiance.custody-cleanup-receipt v1 schemas/custody-cleanup-receipt.schema.json platform lifecycle engagement owner and evidence record

Receipts contain no bearer, password, value-derived fingerprint, Secret data, or registry body. The projection receipt carries an opaque lease id, canonical receipt id, exact identity roles and mount paths, contract digests, times, resource names, and Kubernetes UIDs. Changing any canonical field invalidates the receipt id.

Broker gate owned directly by Whitehat

The owner does not need a coding agent to translate the request. Given the final projection contract, it can run:

python3 scripts/wp0025-broker-readiness.py show \
  --contract /path/to/final-projection-contract.json

python3 scripts/wp0025-broker-readiness.py verify \
  --contract /path/to/final-projection-contract.json \
  --consumer-root /path/to/whitehat-security

python3 scripts/wp0025-broker-readiness.py approve \
  --contract /path/to/final-projection-contract.json \
  --consumer-root /path/to/whitehat-security \
  --reviewer whitehat-owner

Verification is closed to src/whitehat_security/platform_custody.py and tests/test_platform_custody_adapter.py. It records the full consumer commit, adapter SHA-256, focused-test result, exact platform schema hashes, mount paths, and cleanup support. Approval posts the canonical receipt directly to State Hub as from_agent=whitehat-security. A stale schema, changed projection contract, wrong engagement, wrong target, absent adapter, or failing focused test does not count.

The platform lifecycle queries that receipt before token generation and before the first mutation. State Hub coordination identity is not a secret transport; the receipt contains no secret.

Platform commands

The template at docs/custody-projection-contract.example.json intentionally fails validation until every placeholder and the approval status are replaced by the owners.

python3 scripts/custody-projection.py validate \
  --contract /path/to/final-projection-contract.json

python3 scripts/custody-projection.py render \
  --contract /path/to/final-projection-contract.json

python3 scripts/custody-projection.py preflight \
  --contract /path/to/final-projection-contract.json

render is value-free and creates no resource. preflight reads only status, metadata, names, image/readiness, and the broker receipt. It never reads a Kubernetes Secret value.

Inside the approved projection interval, an attached custody operator may run:

python3 scripts/custody-projection.py project \
  --contract /path/to/final-projection-contract.json \
  --confirm '<engagement-id>:attended' \
  --receipt-out /approved/non-repo/path/projection.json

If any mutation fails, the transaction invokes exact-scope cleanup. The operator must not release the consumer until the returned projection receipt passes its adapter. The receipt file is written mode 0600 even though it is value-safe.

Status and normal attended cleanup require that receipt:

python3 scripts/custody-projection.py status \
  --contract /path/to/final-projection-contract.json \
  --receipt /approved/non-repo/path/projection.json

python3 scripts/custody-projection.py cleanup \
  --contract /path/to/final-projection-contract.json \
  --receipt /approved/non-repo/path/projection.json \
  --confirm '<engagement-id>:cleanup' \
  --receipt-out /approved/non-repo/path/cleanup.json

Status reports active, absent, partial, or mismatched. It compares live resource UIDs with the receipt and does not interpret a connection error as absence.

Expired cleanup entry point

cleanup-expired refuses until the contract expiry and still requires the exact projection receipt and confirmation:

python3 scripts/custody-projection.py cleanup-expired \
  --contract /path/to/final-projection-contract.json \
  --receipt /approved/non-repo/path/projection.json \
  --confirm '<engagement-id>:expired' \
  --receipt-out /approved/non-repo/path/cleanup.json

This is an approval-ready reaper entry point, not a deployed scheduler. A future controller still needs an owner-reviewed credential route, runtime identity, alerting, and rollout decision. Application expires_at remains the acceptance backstop; evidenced resource deletion remains the cleanup result.