Prepare receipt-bound audit E2 third attempt
Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a02991-be07-7bb3-8b6d-e9701b5621de
This commit is contained in:
parent
0525e632d7
commit
96d6781036
12 changed files with 440 additions and 27 deletions
|
|
@ -27,3 +27,4 @@
|
||||||
| task | WHITEHAT-WP-0003-T01 | done | — | workplans/WHITEHAT-WP-0003-platform-custody-adapter.md |
|
| task | WHITEHAT-WP-0003-T01 | done | — | workplans/WHITEHAT-WP-0003-platform-custody-adapter.md |
|
||||||
| task | WHITEHAT-WP-0004-T01 | done | — | workplans/WHITEHAT-WP-0004-canonical-custody-binding.md |
|
| task | WHITEHAT-WP-0004-T01 | done | — | workplans/WHITEHAT-WP-0004-canonical-custody-binding.md |
|
||||||
| task | WHITEHAT-WP-0005-T01 | done | — | workplans/WHITEHAT-WP-0005-broker-receipt-admission.md |
|
| task | WHITEHAT-WP-0005-T01 | done | — | workplans/WHITEHAT-WP-0005-broker-receipt-admission.md |
|
||||||
|
| task | WHITEHAT-WP-0005-T02 | done | — | workplans/WHITEHAT-WP-0005-broker-receipt-admission.md |
|
||||||
|
|
|
||||||
69
engagements/2026-08-22-audit-core-e2-03.json
Normal file
69
engagements/2026-08-22-audit-core-e2-03.json
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
{
|
||||||
|
"engagement_id": "WH-ENG-20260822-AUDIT-E2-03",
|
||||||
|
"status": "proposed",
|
||||||
|
"proposal_at": "2026-08-22T21:15:00Z",
|
||||||
|
"proposal_reason": "Fresh attended attempt after -02 proved exact cleanup and exposed the now-implemented WP-0025 custody receipt handoff. No terminal identifier, identity, tenant, object, or Kubernetes Secret is reused.",
|
||||||
|
"authorization_id": "operator-session-2026-08-22-e2-03-approval",
|
||||||
|
"authorizer": "pending explicit repository operator approval",
|
||||||
|
"approved_at": "",
|
||||||
|
"expires_at": "2026-08-22T22:15:00Z",
|
||||||
|
"target": "http://audit-core.audit-core.svc.cluster.local:8080",
|
||||||
|
"target_id": "audit-core",
|
||||||
|
"target_owner": "audit-core / infrastructure operator",
|
||||||
|
"target_owner_acknowledged_at": "",
|
||||||
|
"target_owner_acknowledgement_status": "pending direct review after operator approval",
|
||||||
|
"environment": "production",
|
||||||
|
"production_approval": "pending explicit approval of this exact -03 package",
|
||||||
|
"approval_class": "live-e2",
|
||||||
|
"plane_namespace": "whitehat",
|
||||||
|
"runner_image_digest": "sha256:c2fe39a0185b99be3fc0cb14d2de69772b8e66e20490097c9d11d90cc39719a6",
|
||||||
|
"namespace": "whitehat",
|
||||||
|
"source": "dedicated pod whitehat/whitehat-e2-audit-03, labels whitehat.security/plane=true, whitehat.security/target=audit-core, whitehat.security/engagement=WH-ENG-20260822-AUDIT-E2-03, serviceAccount whitehat-runner, automountServiceAccountToken=false",
|
||||||
|
"source_image": "forgejo.coulomb.social/coulomb/audit-core@sha256:c2fe39a0185b99be3fc0cb14d2de69772b8e66e20490097c9d11d90cc39719a6",
|
||||||
|
"control_plane_scope": [
|
||||||
|
"GET apps/v1 deployment audit-core/audit-core for target revision only",
|
||||||
|
"GET v1 service audit-core/audit-core for target identity only",
|
||||||
|
"CREATE/GET/EXEC/DELETE v1 pod whitehat/whitehat-e2-audit-03 only; EXEC may stream runners/audit_core_e2.py to python3 stdin and invoke no other program",
|
||||||
|
"mount but never GET v1 secret whitehat/whitehat-e2-audit-credentials-03 provisioned by the custody owner",
|
||||||
|
"GET pod log whitehat/whitehat-e2-audit-03 only"
|
||||||
|
],
|
||||||
|
"routes": [
|
||||||
|
"GET /readyz",
|
||||||
|
"POST /v1/events",
|
||||||
|
"GET /v1/events/{event_id}",
|
||||||
|
"GET /v1/events?correlation_id={correlation_id}"
|
||||||
|
],
|
||||||
|
"fixture_ids": [
|
||||||
|
"tenant:trial:whitehat-a-20260822-03", "tenant:trial:whitehat-b-20260822-03",
|
||||||
|
"whitehat-e2-event-a-20260822-03", "whitehat-e2-event-b-20260822-03",
|
||||||
|
"whitehat-e2-absent-20260822-03", "whitehat-e2-forged-b-20260822-03",
|
||||||
|
"whitehat-e2-correlation-20260822-03"
|
||||||
|
],
|
||||||
|
"credential_lane": "RAILIANCE-WP-0025 canonical value-safe custody receipts; no credential value retained",
|
||||||
|
"credential_role": "two temporary ordinary tenant-scoped may_read/may_write fixture senders bound to source=whitehat-security",
|
||||||
|
"credential_max_ttl_seconds": 900,
|
||||||
|
"techniques": ["e2-differential"],
|
||||||
|
"prohibited_techniques": [
|
||||||
|
"discovery", "direct-database-access", "saturation", "large-payload",
|
||||||
|
"real-data-collection", "adjacent-service-follow-up",
|
||||||
|
"any control-plane operation outside control_plane_scope"
|
||||||
|
],
|
||||||
|
"rate_limit_per_minute": 10,
|
||||||
|
"max_concurrency": 1,
|
||||||
|
"maximum_requests": 30,
|
||||||
|
"window_start": "2026-08-22T22:00:00Z",
|
||||||
|
"window_end": "2026-08-22T22:15:00Z",
|
||||||
|
"operator_contact": "repository operator in coordinating session",
|
||||||
|
"abort_contact": "repository operator in coordinating session",
|
||||||
|
"abort_thresholds": {
|
||||||
|
"p95_latency_ms": 500,
|
||||||
|
"probe_attributable_5xx": 1,
|
||||||
|
"unexpected_or_non_fixture_data": 1,
|
||||||
|
"missing_attribution": 1
|
||||||
|
},
|
||||||
|
"posture_claim": "implemented E2; currently evidenced E1",
|
||||||
|
"attacker_model": "E2-authenticated-tenant-a",
|
||||||
|
"finding_destination": "risk-nexus",
|
||||||
|
"plane_bundle_digest": "9636f48f0b994118ff60a8c014e0099486945d66a2b3d3582dc57a09862b2035",
|
||||||
|
"target_ingress": "audit-core-whitehat-ingress at audit-core 5b5196e; namespace=whitehat AND whitehat.security/plane=true AND whitehat.security/target=audit-core"
|
||||||
|
}
|
||||||
13
engagements/2026-08-22-audit-core-e2-03.md
Normal file
13
engagements/2026-08-22-audit-core-e2-03.md
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
# WH-ENG-20260822-AUDIT-E2-03
|
||||||
|
|
||||||
|
Status: **proposed** for `2026-08-22T22:00:00Z`–`22:15:00Z`.
|
||||||
|
|
||||||
|
This is a fresh, terminal-unique attempt. It does not reuse the `-01` or `-02`
|
||||||
|
identifier, senders, tenants, objects, mounted Secret, or runner pod. The
|
||||||
|
projection gate closes at `22:03:00Z`; all custody resources expire and must be
|
||||||
|
cleaned by `22:15:00Z`.
|
||||||
|
|
||||||
|
The run uses the canonical RAILIANCE-WP-0025 projection contract, Whitehat
|
||||||
|
broker-readiness receipt, projection receipt, contract-bound `admit-plane`,
|
||||||
|
cleanup receipt, and finalized report. No live action is authorized while this
|
||||||
|
record remains proposed.
|
||||||
|
|
@ -1,16 +1,20 @@
|
||||||
# Projection receipts
|
# Projection receipts
|
||||||
|
|
||||||
Copy `example-projection-receipt.json` at window start and fill in the live
|
Legacy value-safe receipts remain supported for historical fixtures. New live
|
||||||
engagement id, `projected_at`, `expires_at`, and identity *names* from the
|
engagements use the canonical RAILIANCE-WP-0025 contract, broker-readiness,
|
||||||
custody procedure. Never put token values, passwords, or registry payloads
|
projection, and cleanup receipts. Never hand-edit a live receipt or put token
|
||||||
here.
|
values, passwords, or registry payloads here.
|
||||||
|
|
||||||
Live admission:
|
Live admission:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
PYTHONPATH=src python3 -m whitehat_security.cli admit-plane \
|
PYTHONPATH=src python3 -m whitehat_security.cli admit-plane \
|
||||||
engagements/<record>.json targets/audit-core-e2.json \
|
engagements/<record>.json targets/audit-core-e2.json \
|
||||||
--receipt engagements/receipts/<engagement>.json
|
--receipt /approved/path/projection.json \
|
||||||
|
--contract /approved/path/projection-contract.json \
|
||||||
|
--broker-receipt /approved/path/broker.json
|
||||||
```
|
```
|
||||||
|
|
||||||
Without `--receipt`, live admission fails closed and requests no credential.
|
Without all three bound documents, WP-0025 admission fails closed and requests
|
||||||
|
no credential. After platform cleanup, use `finalize-report` with the cleanup
|
||||||
|
receipt before delivery.
|
||||||
|
|
|
||||||
113
engagements/runtime/WH-ENG-20260822-AUDIT-E2-03-invocation.md
Normal file
113
engagements/runtime/WH-ENG-20260822-AUDIT-E2-03-invocation.md
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
# WH-ENG-20260822-AUDIT-E2-03 — attended invocation
|
||||||
|
|
||||||
|
This package is proposed for `22:00Z`–`22:15Z`; projection closes at `22:03Z`.
|
||||||
|
Do nothing live until the engagement and the exact WP-0025 contract are
|
||||||
|
approved and audit-core has acknowledged them.
|
||||||
|
|
||||||
|
Runtime value-safe artifacts are written beneath
|
||||||
|
`/tmp/WH-ENG-20260822-AUDIT-E2-03/`:
|
||||||
|
|
||||||
|
- `broker.json`
|
||||||
|
- `projection.json`
|
||||||
|
- `runner-report.json`
|
||||||
|
- `cleanup.json`
|
||||||
|
- `final-report.json`
|
||||||
|
|
||||||
|
The platform contract is
|
||||||
|
`/home/worsch/railiance-platform/docs/WH-ENG-20260822-AUDIT-E2-03-custody-contract.json`.
|
||||||
|
|
||||||
|
## Broker gate, projection, and admission
|
||||||
|
|
||||||
|
From `railiance-platform`, after approval and before the window:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 scripts/wp0025-broker-readiness.py approve \
|
||||||
|
--contract docs/WH-ENG-20260822-AUDIT-E2-03-custody-contract.json \
|
||||||
|
--consumer-root /home/worsch/whitehat-security \
|
||||||
|
--reviewer whitehat-owner \
|
||||||
|
--receipt-out /tmp/WH-ENG-20260822-AUDIT-E2-03/broker.json
|
||||||
|
|
||||||
|
python3 scripts/custody-projection.py preflight \
|
||||||
|
--contract docs/WH-ENG-20260822-AUDIT-E2-03-custody-contract.json
|
||||||
|
```
|
||||||
|
|
||||||
|
At or after `22:00Z`, no later than `22:03Z`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 scripts/custody-projection.py project \
|
||||||
|
--contract docs/WH-ENG-20260822-AUDIT-E2-03-custody-contract.json \
|
||||||
|
--confirm WH-ENG-20260822-AUDIT-E2-03:attended \
|
||||||
|
--receipt-out /tmp/WH-ENG-20260822-AUDIT-E2-03/projection.json
|
||||||
|
```
|
||||||
|
|
||||||
|
From `whitehat-security`, admit before applying the runner:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
PYTHONPATH=src python3 -m whitehat_security.cli admit-plane \
|
||||||
|
engagements/2026-08-22-audit-core-e2-03.json \
|
||||||
|
targets/audit-core-e2.json \
|
||||||
|
--receipt /tmp/WH-ENG-20260822-AUDIT-E2-03/projection.json \
|
||||||
|
--contract /home/worsch/railiance-platform/docs/WH-ENG-20260822-AUDIT-E2-03-custody-contract.json \
|
||||||
|
--broker-receipt /tmp/WH-ENG-20260822-AUDIT-E2-03/broker.json
|
||||||
|
```
|
||||||
|
|
||||||
|
## Runner
|
||||||
|
|
||||||
|
Only after admission succeeds:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh -o BatchMode=yes railiance01 kubectl apply -f - \
|
||||||
|
< engagements/runtime/whitehat-e2-audit-20260822-03.yaml
|
||||||
|
ssh -o BatchMode=yes railiance01 kubectl -n whitehat wait \
|
||||||
|
--for=condition=Ready pod/whitehat-e2-audit-03 --timeout=60s
|
||||||
|
```
|
||||||
|
|
||||||
|
Stream `runners/audit_core_e2.py` to `python3 -` inside that pod with these
|
||||||
|
exact arguments:
|
||||||
|
|
||||||
|
```text
|
||||||
|
--base-url http://audit-core.audit-core.svc.cluster.local:8080
|
||||||
|
--engagement-id WH-ENG-20260822-AUDIT-E2-03
|
||||||
|
--authorization-id operator-session-2026-08-22-e2-03-approval
|
||||||
|
--target-revision sha256:c2fe39a0185b99be3fc0cb14d2de69772b8e66e20490097c9d11d90cc39719a6
|
||||||
|
--token-a-file /var/run/secrets/whitehat/token-a
|
||||||
|
--token-b-file /var/run/secrets/whitehat/token-b
|
||||||
|
--tenant-a tenant:trial:whitehat-a-20260822-03
|
||||||
|
--tenant-b tenant:trial:whitehat-b-20260822-03
|
||||||
|
--event-a whitehat-e2-event-a-20260822-03
|
||||||
|
--event-b whitehat-e2-event-b-20260822-03
|
||||||
|
--absent-event whitehat-e2-absent-20260822-03
|
||||||
|
--forged-event whitehat-e2-forged-b-20260822-03
|
||||||
|
--correlation whitehat-e2-correlation-20260822-03
|
||||||
|
--occurred-at 2026-08-22T22:00:00Z
|
||||||
|
--rate 10 --max-requests 30 --abort-p95-ms 500
|
||||||
|
```
|
||||||
|
|
||||||
|
The runner output is the sanitized `runner-report.json`. Delete the runner
|
||||||
|
immediately after execution, whether it passes, finds, or aborts.
|
||||||
|
|
||||||
|
## Cleanup and report finalization
|
||||||
|
|
||||||
|
From `railiance-platform`, before `22:15Z`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 scripts/custody-projection.py cleanup \
|
||||||
|
--contract docs/WH-ENG-20260822-AUDIT-E2-03-custody-contract.json \
|
||||||
|
--receipt /tmp/WH-ENG-20260822-AUDIT-E2-03/projection.json \
|
||||||
|
--confirm WH-ENG-20260822-AUDIT-E2-03:cleanup \
|
||||||
|
--receipt-out /tmp/WH-ENG-20260822-AUDIT-E2-03/cleanup.json
|
||||||
|
```
|
||||||
|
|
||||||
|
From `whitehat-security`, bind the report to the cleanup receipt and deliver it:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
PYTHONPATH=src python3 -m whitehat_security.cli finalize-report \
|
||||||
|
/tmp/WH-ENG-20260822-AUDIT-E2-03/runner-report.json \
|
||||||
|
--contract /home/worsch/railiance-platform/docs/WH-ENG-20260822-AUDIT-E2-03-custody-contract.json \
|
||||||
|
--receipt /tmp/WH-ENG-20260822-AUDIT-E2-03/projection.json \
|
||||||
|
--cleanup-receipt /tmp/WH-ENG-20260822-AUDIT-E2-03/cleanup.json \
|
||||||
|
--output /tmp/WH-ENG-20260822-AUDIT-E2-03/final-report.json
|
||||||
|
|
||||||
|
PYTHONPATH=src python3 -m whitehat_security.cli deliver \
|
||||||
|
/tmp/WH-ENG-20260822-AUDIT-E2-03/final-report.json --outbox outbox
|
||||||
|
```
|
||||||
51
engagements/runtime/whitehat-e2-audit-20260822-03.yaml
Normal file
51
engagements/runtime/whitehat-e2-audit-20260822-03.yaml
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
# CONTRACT ONLY. Do not apply until WP-0025 projection and admit-plane succeed.
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Pod
|
||||||
|
metadata:
|
||||||
|
name: whitehat-e2-audit-03
|
||||||
|
namespace: whitehat
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: whitehat-security
|
||||||
|
app.kubernetes.io/component: whitehat-probe
|
||||||
|
whitehat.security/plane: "true"
|
||||||
|
whitehat.security/target: audit-core
|
||||||
|
whitehat.security/engagement: WH-ENG-20260822-AUDIT-E2-03
|
||||||
|
spec:
|
||||||
|
serviceAccountName: whitehat-runner
|
||||||
|
automountServiceAccountToken: false
|
||||||
|
activeDeadlineSeconds: 900
|
||||||
|
restartPolicy: Never
|
||||||
|
securityContext:
|
||||||
|
runAsNonRoot: true
|
||||||
|
runAsUser: 10001
|
||||||
|
runAsGroup: 10001
|
||||||
|
fsGroup: 10001
|
||||||
|
seccompProfile:
|
||||||
|
type: RuntimeDefault
|
||||||
|
containers:
|
||||||
|
- name: probe
|
||||||
|
image: forgejo.coulomb.social/coulomb/audit-core@sha256:c2fe39a0185b99be3fc0cb14d2de69772b8e66e20490097c9d11d90cc39719a6
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
command: ["sleep", "900"]
|
||||||
|
resources:
|
||||||
|
requests: {cpu: 5m, memory: 16Mi}
|
||||||
|
limits: {cpu: 50m, memory: 64Mi}
|
||||||
|
securityContext:
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
capabilities:
|
||||||
|
drop: ["ALL"]
|
||||||
|
readOnlyRootFilesystem: true
|
||||||
|
volumeMounts:
|
||||||
|
- name: credentials
|
||||||
|
mountPath: /var/run/secrets/whitehat
|
||||||
|
readOnly: true
|
||||||
|
volumes:
|
||||||
|
- name: credentials
|
||||||
|
secret:
|
||||||
|
secretName: whitehat-e2-audit-credentials-03
|
||||||
|
defaultMode: 0440
|
||||||
|
items:
|
||||||
|
- key: token-a
|
||||||
|
path: token-a
|
||||||
|
- key: token-b
|
||||||
|
path: token-b
|
||||||
|
|
@ -183,10 +183,36 @@ def invocation_fixture_ids(args: argparse.Namespace) -> set[str]:
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def build_report(args: argparse.Namespace, *, started: str, outcome: str,
|
||||||
|
attempted_operations: int, results: list[dict[str, Any]],
|
||||||
|
limitations: list[str]) -> dict[str, Any]:
|
||||||
|
return {
|
||||||
|
"schema_version": "whitehat-run/v1",
|
||||||
|
"run_id": f"{args.engagement_id}-{started}",
|
||||||
|
"evidence_class": "target",
|
||||||
|
"engagement_id": args.engagement_id,
|
||||||
|
"authorization_id": args.authorization_id,
|
||||||
|
"target": "audit-core",
|
||||||
|
"target_revision": args.target_revision,
|
||||||
|
"posture_claim": "implemented E2; currently evidenced E1",
|
||||||
|
"attacker_model": "E2-authenticated-tenant-a",
|
||||||
|
"started_at": started,
|
||||||
|
"ended_at": now(),
|
||||||
|
"outcome": outcome,
|
||||||
|
"attempted_operations": attempted_operations,
|
||||||
|
"cleanup": "named immutable audit fixtures retained by target contract",
|
||||||
|
"credential_revocation": "pending orchestrator cleanup",
|
||||||
|
"probes": results,
|
||||||
|
"limitations": limitations,
|
||||||
|
"assurance_statement": ASSURANCE,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument("--base-url", required=True)
|
parser.add_argument("--base-url", required=True)
|
||||||
parser.add_argument("--engagement-id", required=True)
|
parser.add_argument("--engagement-id", required=True)
|
||||||
|
parser.add_argument("--authorization-id", required=True)
|
||||||
parser.add_argument("--target-revision", required=True)
|
parser.add_argument("--target-revision", required=True)
|
||||||
parser.add_argument("--token-a-file", required=True)
|
parser.add_argument("--token-a-file", required=True)
|
||||||
parser.add_argument("--token-b-file", required=True)
|
parser.add_argument("--token-b-file", required=True)
|
||||||
|
|
@ -278,26 +304,14 @@ def main() -> None:
|
||||||
except Abort as error:
|
except Abort as error:
|
||||||
outcome = "aborted"
|
outcome = "aborted"
|
||||||
limitations.append(str(error))
|
limitations.append(str(error))
|
||||||
report = {
|
report = build_report(
|
||||||
"schema_version": "whitehat-run/v1",
|
args,
|
||||||
"run_id": f"{args.engagement_id}-{started}",
|
started=started,
|
||||||
"evidence_class": "target",
|
outcome=outcome,
|
||||||
"engagement_id": args.engagement_id,
|
attempted_operations=client.requests,
|
||||||
"authorization_id": "operator-session-2026-08-21-e2-approval",
|
results=results,
|
||||||
"target": "audit-core",
|
limitations=limitations,
|
||||||
"target_revision": args.target_revision,
|
)
|
||||||
"posture_claim": "implemented E2; currently evidenced E1",
|
|
||||||
"attacker_model": "E2-authenticated-tenant-a",
|
|
||||||
"started_at": started,
|
|
||||||
"ended_at": now(),
|
|
||||||
"outcome": outcome,
|
|
||||||
"attempted_operations": client.requests,
|
|
||||||
"cleanup": "named immutable audit fixtures retained by target contract",
|
|
||||||
"credential_revocation": "pending orchestrator cleanup",
|
|
||||||
"probes": results,
|
|
||||||
"limitations": limitations,
|
|
||||||
"assurance_statement": ASSURANCE,
|
|
||||||
}
|
|
||||||
print(json.dumps(report, sort_keys=True))
|
print(json.dumps(report, sort_keys=True))
|
||||||
raise SystemExit(0 if outcome == "pass" else 1)
|
raise SystemExit(0 if outcome == "pass" else 1)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ from .engagement import AuthorizationError, Engagement
|
||||||
from .fixtures import FixtureService, probe_suite
|
from .fixtures import FixtureService, probe_suite
|
||||||
from .model import RunReport, utc_now
|
from .model import RunReport, utc_now
|
||||||
from .plane import KillSwitch, admit, default_broker, retired_ids
|
from .plane import KillSwitch, admit, default_broker, retired_ids
|
||||||
from .platform_custody import broker_from_receipt
|
from .platform_custody import broker_from_receipt, finalize_run_report
|
||||||
from .reporting import queue_risk_nexus, risk_nexus_message
|
from .reporting import queue_risk_nexus, risk_nexus_message
|
||||||
from .targets import load_catalog, load_registration
|
from .targets import load_catalog, load_registration
|
||||||
|
|
||||||
|
|
@ -100,6 +100,12 @@ def main(argv: list[str] | None = None) -> None:
|
||||||
"--broker-receipt",
|
"--broker-receipt",
|
||||||
help="WP-0025 broker-readiness receipt; required for railiance.custody-projection-receipt",
|
help="WP-0025 broker-readiness receipt; required for railiance.custody-projection-receipt",
|
||||||
)
|
)
|
||||||
|
finalize = commands.add_parser("finalize-report")
|
||||||
|
finalize.add_argument("report")
|
||||||
|
finalize.add_argument("--contract", required=True)
|
||||||
|
finalize.add_argument("--receipt", required=True)
|
||||||
|
finalize.add_argument("--cleanup-receipt", required=True)
|
||||||
|
finalize.add_argument("--output", required=True)
|
||||||
commands.add_parser("kill-switch")
|
commands.add_parser("kill-switch")
|
||||||
deliver = commands.add_parser("deliver")
|
deliver = commands.add_parser("deliver")
|
||||||
deliver.add_argument("report")
|
deliver.add_argument("report")
|
||||||
|
|
@ -171,6 +177,27 @@ def main(argv: list[str] | None = None) -> None:
|
||||||
raise SystemExit(1)
|
raise SystemExit(1)
|
||||||
print("clear")
|
print("clear")
|
||||||
return
|
return
|
||||||
|
if args.command == "finalize-report":
|
||||||
|
try:
|
||||||
|
report = json.loads(Path(args.report).read_text(encoding="utf-8"))
|
||||||
|
contract = json.loads(Path(args.contract).read_text(encoding="utf-8"))
|
||||||
|
projection = json.loads(Path(args.receipt).read_text(encoding="utf-8"))
|
||||||
|
cleanup = json.loads(Path(args.cleanup_receipt).read_text(encoding="utf-8"))
|
||||||
|
finalized = finalize_run_report(
|
||||||
|
report, projection=projection, cleanup=cleanup, contract=contract
|
||||||
|
)
|
||||||
|
RunReport(**finalized)
|
||||||
|
output = Path(args.output)
|
||||||
|
output.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
output.write_text(
|
||||||
|
json.dumps(finalized, indent=2, sort_keys=True) + "\n",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
except (AuthorizationError, OSError, ValueError, json.JSONDecodeError, TypeError) as error:
|
||||||
|
print(f"not authorized: {error}", file=sys.stderr)
|
||||||
|
raise SystemExit(2) from None
|
||||||
|
print(f"finalized: {output}")
|
||||||
|
return
|
||||||
if args.command == "deliver":
|
if args.command == "deliver":
|
||||||
try:
|
try:
|
||||||
report = RunReport(**json.loads(Path(args.report).read_text(encoding="utf-8")))
|
report = RunReport(**json.loads(Path(args.report).read_text(encoding="utf-8")))
|
||||||
|
|
|
||||||
|
|
@ -331,6 +331,32 @@ def validate_cleanup_receipt(
|
||||||
return document
|
return document
|
||||||
|
|
||||||
|
|
||||||
|
def finalize_run_report(
|
||||||
|
report: dict[str, Any], *, projection: dict[str, Any],
|
||||||
|
cleanup: dict[str, Any], contract: dict[str, Any]
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
"""Bind a sanitized runner report to the canonical custody cleanup."""
|
||||||
|
validate_projection_contract(contract)
|
||||||
|
validate_projection_receipt(projection, contract=contract)
|
||||||
|
validate_cleanup_receipt(cleanup, projection=projection, contract=contract)
|
||||||
|
if report.get("engagement_id") != contract["engagement_id"]:
|
||||||
|
raise AuthorizationError("run report engagement does not match custody contract")
|
||||||
|
if report.get("target") != contract["target"]["id"]:
|
||||||
|
raise AuthorizationError("run report target does not match custody contract")
|
||||||
|
if report.get("target_revision") != contract["target"]["image_digest"]:
|
||||||
|
raise AuthorizationError("run report revision does not match custody contract")
|
||||||
|
result = dict(report)
|
||||||
|
result["cleanup"] = (
|
||||||
|
"WP-0025 receipt-bound cleanup completed at "
|
||||||
|
f"{cleanup['cleaned_at']} for projection {projection['receipt_id']}"
|
||||||
|
)
|
||||||
|
result["credential_revocation"] = (
|
||||||
|
"revoked by railiance-platform custody cleanup for lease "
|
||||||
|
f"{projection['lease_id']}"
|
||||||
|
)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
def validate_broker_readiness(
|
def validate_broker_readiness(
|
||||||
document: dict[str, Any], *, contract: dict[str, Any], now: datetime | None = None
|
document: dict[str, Any], *, contract: dict[str, Any], now: datetime | None = None
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
|
|
|
||||||
|
|
@ -84,3 +84,44 @@ def test_retry_invocation_fixture_set_is_declared_and_distinct():
|
||||||
assert fixtures <= set(engagement["fixture_ids"])
|
assert fixtures <= set(engagement["fixture_ids"])
|
||||||
assert fixtures.isdisjoint(previous["fixture_ids"])
|
assert fixtures.isdisjoint(previous["fixture_ids"])
|
||||||
assert engagement["engagement_id"] != previous["engagement_id"]
|
assert engagement["engagement_id"] != previous["engagement_id"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_report_uses_supplied_authorization_id():
|
||||||
|
args = Namespace(
|
||||||
|
engagement_id="WH-ENG-EXAMPLE",
|
||||||
|
authorization_id="operator-session-example",
|
||||||
|
target_revision="sha256:" + "a" * 64,
|
||||||
|
)
|
||||||
|
report = runner.build_report(
|
||||||
|
args,
|
||||||
|
started="2026-08-22T22:00:00Z",
|
||||||
|
outcome="pass",
|
||||||
|
attempted_operations=9,
|
||||||
|
results=[],
|
||||||
|
limitations=[],
|
||||||
|
)
|
||||||
|
assert report["authorization_id"] == "operator-session-example"
|
||||||
|
assert report["engagement_id"] == "WH-ENG-EXAMPLE"
|
||||||
|
assert report["attempted_operations"] == 9
|
||||||
|
|
||||||
|
|
||||||
|
def test_third_attempt_fixtures_are_new_and_complete():
|
||||||
|
previous = json.loads(
|
||||||
|
Path("engagements/2026-08-22-audit-core-e2-02.json").read_text()
|
||||||
|
)
|
||||||
|
engagement = json.loads(
|
||||||
|
Path("engagements/2026-08-22-audit-core-e2-03.json").read_text()
|
||||||
|
)
|
||||||
|
args = Namespace(
|
||||||
|
tenant_a="tenant:trial:whitehat-a-20260822-03",
|
||||||
|
tenant_b="tenant:trial:whitehat-b-20260822-03",
|
||||||
|
event_a="whitehat-e2-event-a-20260822-03",
|
||||||
|
event_b="whitehat-e2-event-b-20260822-03",
|
||||||
|
absent_event="whitehat-e2-absent-20260822-03",
|
||||||
|
forged_event="whitehat-e2-forged-b-20260822-03",
|
||||||
|
correlation="whitehat-e2-correlation-20260822-03",
|
||||||
|
)
|
||||||
|
fixtures = runner.invocation_fixture_ids(args)
|
||||||
|
assert fixtures <= set(engagement["fixture_ids"])
|
||||||
|
assert fixtures.isdisjoint(previous["fixture_ids"])
|
||||||
|
assert engagement["engagement_id"] != previous["engagement_id"]
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ from whitehat_security.platform_custody import (
|
||||||
broker_from_receipt,
|
broker_from_receipt,
|
||||||
contract_digest,
|
contract_digest,
|
||||||
digest,
|
digest,
|
||||||
|
finalize_run_report,
|
||||||
interface_artifacts,
|
interface_artifacts,
|
||||||
load_schemas,
|
load_schemas,
|
||||||
resource_names,
|
resource_names,
|
||||||
|
|
@ -381,3 +382,38 @@ def test_cleanup_receipt_must_match_lease():
|
||||||
bad = cleanup_receipt(bound, projection)
|
bad = cleanup_receipt(bound, projection)
|
||||||
bad["lease_id"] = "custody:" + "0" * 32
|
bad["lease_id"] = "custody:" + "0" * 32
|
||||||
validate_cleanup_receipt(bad, projection=projection, contract=bound)
|
validate_cleanup_receipt(bad, projection=projection, contract=bound)
|
||||||
|
|
||||||
|
|
||||||
|
def test_finalized_report_is_bound_to_cleanup_receipt():
|
||||||
|
bound = contract()
|
||||||
|
projection = projection_receipt(bound)
|
||||||
|
cleanup = cleanup_receipt(bound, projection)
|
||||||
|
report = {
|
||||||
|
"engagement_id": bound["engagement_id"],
|
||||||
|
"target": bound["target"]["id"],
|
||||||
|
"target_revision": bound["target"]["image_digest"],
|
||||||
|
"credential_revocation": "pending orchestrator cleanup",
|
||||||
|
"cleanup": "pending",
|
||||||
|
}
|
||||||
|
finalized = finalize_run_report(
|
||||||
|
report, projection=projection, cleanup=cleanup, contract=bound
|
||||||
|
)
|
||||||
|
assert finalized["credential_revocation"].startswith("revoked by railiance-platform")
|
||||||
|
assert projection["receipt_id"] in finalized["cleanup"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_finalized_report_refuses_wrong_engagement():
|
||||||
|
bound = contract()
|
||||||
|
projection = projection_receipt(bound)
|
||||||
|
cleanup = cleanup_receipt(bound, projection)
|
||||||
|
with pytest.raises(AuthorizationError, match="report engagement"):
|
||||||
|
finalize_run_report(
|
||||||
|
{
|
||||||
|
"engagement_id": "WH-ENG-WRONG",
|
||||||
|
"target": "audit-core",
|
||||||
|
"target_revision": bound["target"]["image_digest"],
|
||||||
|
},
|
||||||
|
projection=projection,
|
||||||
|
cleanup=cleanup,
|
||||||
|
contract=bound,
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -48,3 +48,21 @@ state_hub_task_id: "a6209f8a-b767-5ce8-8842-c9e454977f83"
|
||||||
Add required `--broker-receipt` for WP-0025 admission, pass it through
|
Add required `--broker-receipt` for WP-0025 admission, pass it through
|
||||||
`broker_from_receipt`, and require exact digest equality. CLI tests cover
|
`broker_from_receipt`, and require exact digest equality. CLI tests cover
|
||||||
a missing broker receipt and a mismatched recanonicalized digest.
|
a missing broker receipt and a mismatched recanonicalized digest.
|
||||||
|
|
||||||
|
### T02 — Bind runner authorization and final report to cleanup
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: WHITEHAT-WP-0005-T02
|
||||||
|
status: done
|
||||||
|
priority: high
|
||||||
|
```
|
||||||
|
|
||||||
|
Fresh-run setup found two remaining operational misbindings. The audit-core
|
||||||
|
runner hard-coded the authorization id from the original 2026-08-21 attempt,
|
||||||
|
and its sanitized report could only say credential cleanup was pending.
|
||||||
|
|
||||||
|
The runner now requires `--authorization-id` and carries that exact value into
|
||||||
|
the report. `finalize-report` validates the WP-0025 projection contract,
|
||||||
|
projection receipt, and cleanup receipt, binds their engagement, target, image,
|
||||||
|
lease, and canonical receipt id to the runner report, and only then records
|
||||||
|
custody revocation as complete. Mismatched reports fail closed.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue