From 481ed6add568a83c7093fbbb85886f2e9d480c70 Mon Sep 17 00:00:00 2001 From: tegwick Date: Sat, 22 Aug 2026 21:40:33 +0200 Subject: [PATCH] Add WHITEHAT-WP-0002 receipt example, CLI coverage, and abort records Meantime polish while live E2 waits on a new trial. Example receipts carry handles only. admit-plane --receipt is tested. Aborts can be queued without being target assurance. Assistant: grok Assistant-Session: 01a02670-3345-76f2-a014-70fde8e2a2bb --- SCOPE.md | 2 +- engagements/receipts/README.md | 16 +++ .../receipts/example-projection-receipt.json | 14 +++ evidence/README.md | 3 +- .../WH-ENG-20260822-AUDIT-E2-02-abort.json | 23 ++++ schemas/run-report.schema.json | 2 +- src/whitehat_security/model.py | 2 +- src/whitehat_security/reporting.py | 6 +- tests/test_cli.py | 104 ++++++++++++++++++ tests/test_e3_capacity_reporting.py | 15 +++ ...002-receipt-admission-and-abort-records.md | 65 +++++++++++ 11 files changed, 247 insertions(+), 5 deletions(-) create mode 100644 engagements/receipts/README.md create mode 100644 engagements/receipts/example-projection-receipt.json create mode 100644 evidence/WH-ENG-20260822-AUDIT-E2-02-abort.json create mode 100644 workplans/WHITEHAT-WP-0002-receipt-admission-and-abort-records.md diff --git a/SCOPE.md b/SCOPE.md index 8ec488b..6ada513 100644 --- a/SCOPE.md +++ b/SCOPE.md @@ -65,7 +65,7 @@ boundary always holds. ## Current state - Repository status: active. -- Active plan: `WHITEHAT-WP-0001`. +- Active plan: `WHITEHAT-WP-0001`. Meantime polish: `WHITEHAT-WP-0002`. - `T01` is complete: the rules of engagement were accepted on 2026-08-21. - `T02` is complete: the per-axis attacker model is recorded in `docs/attacker-model.md`. diff --git a/engagements/receipts/README.md b/engagements/receipts/README.md new file mode 100644 index 0000000..3879c39 --- /dev/null +++ b/engagements/receipts/README.md @@ -0,0 +1,16 @@ +# Projection receipts + +Copy `example-projection-receipt.json` at window start and fill in the live +engagement id, `projected_at`, `expires_at`, and identity *names* from the +custody procedure. Never put token values, passwords, or registry payloads +here. + +Live admission: + +```sh +PYTHONPATH=src python3 -m whitehat_security.cli admit-plane \ + engagements/.json targets/audit-core-e2.json \ + --receipt engagements/receipts/.json +``` + +Without `--receipt`, live admission fails closed and requests no credential. diff --git a/engagements/receipts/example-projection-receipt.json b/engagements/receipts/example-projection-receipt.json new file mode 100644 index 0000000..c8eb406 --- /dev/null +++ b/engagements/receipts/example-projection-receipt.json @@ -0,0 +1,14 @@ +{ + "engagement_id": "WH-ENG-EXAMPLE", + "projected_at": "2026-08-22T19:17:54Z", + "expires_at": "2099-01-01T00:00:00Z", + "identities": [ + "whitehat-e2-a-example", + "whitehat-e2-b-example" + ], + "mounted_secret": "whitehat/whitehat-e2-audit-credentials", + "mounted_keys": ["token-a", "token-b"], + "target_image_matches": true, + "target_ready": true, + "secret_values_observed": false +} diff --git a/evidence/README.md b/evidence/README.md index e7f7605..df58f1f 100644 --- a/evidence/README.md +++ b/evidence/README.md @@ -3,7 +3,8 @@ This directory stores sanitized run artifacts. `offline-calibration.json` and `offline-e3-calibration.json` are generated from repository-created fixtures and prove only that the harness distinguishes known-good from known-bad -behavior. They are not target assurance. +behavior. They are not target assurance. `WH-ENG-20260822-AUDIT-E2-02-abort.json` +is an abort record (`evidence_class: abort`), not an E2 pass or finding. Before committing target evidence, verify that it contains no response body, credential, database URL, real tenant identifier, or real tenant value. A diff --git a/evidence/WH-ENG-20260822-AUDIT-E2-02-abort.json b/evidence/WH-ENG-20260822-AUDIT-E2-02-abort.json new file mode 100644 index 0000000..fb20f27 --- /dev/null +++ b/evidence/WH-ENG-20260822-AUDIT-E2-02-abort.json @@ -0,0 +1,23 @@ +{ + "schema_version": "whitehat-run/v1", + "run_id": "WH-ENG-20260822-AUDIT-E2-02-abort", + "evidence_class": "abort", + "engagement_id": "WH-ENG-20260822-AUDIT-E2-02", + "authorization_id": "operator-session-2026-08-22-e2-02-approval", + "target": "audit-core", + "target_revision": "sha256:c2fe39a0185b99be3fc0cb14d2de69772b8e66e20490097c9d11d90cc39719a6", + "posture_claim": "implemented E2; currently evidenced E1", + "attacker_model": "E2-authenticated-tenant-a", + "started_at": "2026-08-22T19:17:54Z", + "ended_at": "2026-08-22T19:21:39Z", + "outcome": "aborted", + "attempted_operations": 0, + "cleanup": "runner deleted 19:21:07Z; custody cleanup 19:21:39Z; no leftover Secret, KV paths, or identities", + "credential_revocation": "custody-owned; receipt broker does not hold credentials", + "probes": [], + "limitations": [ + "admit-plane had no receipt adapter at run time; the runner was never invoked.", + "Zero packets were sent. This is not E2 evidence." + ], + "assurance_statement": "Pass means only that the attacks attempted in this run did not work; it is not proof that the tenant boundary always holds." +} diff --git a/schemas/run-report.schema.json b/schemas/run-report.schema.json index 3102a43..910e093 100644 --- a/schemas/run-report.schema.json +++ b/schemas/run-report.schema.json @@ -11,7 +11,7 @@ ], "properties": { "schema_version": {"const": "whitehat-run/v1"}, - "evidence_class": {"enum": ["fixture", "target"]}, + "evidence_class": {"enum": ["fixture", "target", "abort"]}, "outcome": {"enum": ["pass", "finding", "inconclusive", "aborted"]}, "attempted_operations": {"type": "integer", "minimum": 0}, "probes": {"type": "array"}, diff --git a/src/whitehat_security/model.py b/src/whitehat_security/model.py index 9e6d403..f8ff825 100644 --- a/src/whitehat_security/model.py +++ b/src/whitehat_security/model.py @@ -102,7 +102,7 @@ class ProbeResult: class RunReport: schema_version: str run_id: str - evidence_class: Literal["fixture", "target"] + evidence_class: Literal["fixture", "target", "abort"] engagement_id: str authorization_id: str target: str diff --git a/src/whitehat_security/reporting.py b/src/whitehat_security/reporting.py index fd903f0..5a8d274 100644 --- a/src/whitehat_security/reporting.py +++ b/src/whitehat_security/reporting.py @@ -28,13 +28,17 @@ def risk_nexus_message(report: RunReport) -> str: ] if report.outcome == "finding": lines.extend(["", "Reporter supplies facts only; risk-nexus owns severity and disclosure."]) + if report.evidence_class == "abort": + lines.extend(["", "This is an abort record, not target assurance."]) return "\n".join(lines) + "\n" def queue_risk_nexus(report: RunReport, outbox: str | Path) -> Path: """Persist a delivery artifact. Fixture calibration is not target assurance.""" - if report.evidence_class != "target": + if report.evidence_class == "fixture": raise AuthorizationError("fixture evidence is not delivered as target assurance") + if report.evidence_class not in {"target", "abort"}: + raise AuthorizationError("unsupported evidence class for delivery") directory = Path(outbox) directory.mkdir(parents=True, exist_ok=True) path = directory / f"{report.run_id}.md" diff --git a/tests/test_cli.py b/tests/test_cli.py index ecb2570..3965eae 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1,8 +1,10 @@ import json +from pathlib import Path import pytest from whitehat_security.cli import main +from whitehat_security.plane import ReceiptBroker def test_validate_engagement_reports_clean_denial(tmp_path, capsys): @@ -57,6 +59,108 @@ def test_admit_plane_refuses_cancelled_engagement(capsys): assert "not authorized:" in capsys.readouterr().err +def _live_e2_record(): + return { + "engagement_id": "WH-ENG-CLI-RECEIPT", + "authorization_id": "auth-cli", + "authorizer": "operator", + "approved_at": "2026-08-22T00:00:00Z", + "expires_at": "2099-01-01T00:00:00Z", + "target": "https://fixture.invalid", + "target_id": "audit-core", + "target_owner": "audit-core", + "environment": "build", + "source": "runner", + "approval_class": "live-e2", + "plane_namespace": "whitehat", + "runner_image_digest": "sha256:abc", + "routes": ["POST /v1/events"], + "fixture_ids": ["object-a", "object-b"], + "credential_lane": "receipt", + "credential_role": "runtime", + "credential_max_ttl_seconds": 900, + "techniques": ["e2-differential"], + "prohibited_techniques": ["saturation"], + "rate_limit_per_minute": 10, + "max_concurrency": 1, + "maximum_requests": 8, + "window_start": "2026-08-22T00:00:00Z", + "window_end": "2099-01-01T00:00:00Z", + "operator_contact": "operator", + "abort_contact": "operator", + "posture_claim": "E2", + "attacker_model": "E2-authenticated-tenant-a", + "finding_destination": "risk-nexus", + "target_owner_acknowledged_at": "2026-08-22T00:01:00Z", + } + + +def _receipt(**overrides): + data = { + "engagement_id": "WH-ENG-CLI-RECEIPT", + "projected_at": "2026-08-22T19:17:54Z", + "expires_at": "2099-01-01T00:00:00Z", + "identities": ["whitehat-e2-a-example", "whitehat-e2-b-example"], + "mounted_secret": "whitehat/whitehat-e2-audit-credentials", + "mounted_keys": ["token-a", "token-b"], + "target_ready": True, + "secret_values_observed": False, + } + data.update(overrides) + return data + + +def test_admit_plane_without_receipt_still_fails_closed(tmp_path, capsys): + path = tmp_path / "engagement.json" + path.write_text(json.dumps(_live_e2_record()), encoding="utf-8") + with pytest.raises(SystemExit) as stopped: + main(["admit-plane", str(path), "targets/audit-core-e2.json"]) + assert stopped.value.code == 2 + assert "no credential was requested" in capsys.readouterr().err + + +def test_admit_plane_receipt_issues_lease(tmp_path, capsys): + engagement = tmp_path / "engagement.json" + receipt = tmp_path / "receipt.json" + engagement.write_text(json.dumps(_live_e2_record()), encoding="utf-8") + receipt.write_text(json.dumps(_receipt()), encoding="utf-8") + main(["admit-plane", str(engagement), "targets/audit-core-e2.json", "--receipt", str(receipt)]) + out = capsys.readouterr().out + assert out.startswith("admitted: WH-ENG-CLI-RECEIPT") + + +def test_admit_plane_receipt_refuses_secret_material(tmp_path, capsys): + engagement = tmp_path / "engagement.json" + receipt = tmp_path / "receipt.json" + engagement.write_text(json.dumps(_live_e2_record()), encoding="utf-8") + receipt.write_text(json.dumps(_receipt(token="must-not-appear")), encoding="utf-8") + with pytest.raises(SystemExit) as stopped: + main(["admit-plane", str(engagement), "targets/audit-core-e2.json", "--receipt", str(receipt)]) + assert stopped.value.code == 2 + assert "secret material" in capsys.readouterr().err + + +def test_example_projection_receipt_is_value_safe(): + broker = ReceiptBroker.load("engagements/receipts/example-projection-receipt.json") + assert broker.receipt["engagement_id"] == "WH-ENG-EXAMPLE" + assert broker.receipt["secret_values_observed"] is False + assert broker.receipt["mounted_keys"] == ["token-a", "token-b"] + + +def test_deliver_queues_abort_without_calling_it_target_assurance(tmp_path, capsys): + report = json.loads( + Path("evidence/WH-ENG-20260822-AUDIT-E2-02-abort.json").read_text(encoding="utf-8") + ) + path = tmp_path / "abort.json" + path.write_text(json.dumps(report), encoding="utf-8") + main(["deliver", str(path), "--outbox", str(tmp_path / "outbox")]) + queued = (tmp_path / "outbox" / f"{report['run_id']}.md").read_text(encoding="utf-8") + assert capsys.readouterr().out.startswith("queued:") + assert "abort" in queued + assert "not target assurance" in queued + assert "Severity" not in queued + + def test_deliver_refuses_fixture_calibration(tmp_path, capsys): report = tmp_path / "fixture.json" report.write_text(json.dumps({ diff --git a/tests/test_e3_capacity_reporting.py b/tests/test_e3_capacity_reporting.py index 6d52647..ec49227 100644 --- a/tests/test_e3_capacity_reporting.py +++ b/tests/test_e3_capacity_reporting.py @@ -48,3 +48,18 @@ def test_risk_message_contains_pass_and_no_severity(): assert "**pass**" in message assert "Severity" not in message assert "not proof" in message + + +def test_abort_message_is_not_target_assurance(): + report = RunReport( + schema_version="whitehat-run/v1", run_id="abort-1", evidence_class="abort", + engagement_id="eng-1", authorization_id="auth-1", target="audit-core", + target_revision="abc", posture_claim="E2", attacker_model="E2", + started_at="2026-08-22T19:17:54Z", ended_at="2026-08-22T19:21:39Z", + outcome="aborted", attempted_operations=0, cleanup="complete", + credential_revocation="custody-owned", + ) + message = risk_nexus_message(report) + assert "`abort`" in message + assert "not target assurance" in message + assert "Severity" not in message diff --git a/workplans/WHITEHAT-WP-0002-receipt-admission-and-abort-records.md b/workplans/WHITEHAT-WP-0002-receipt-admission-and-abort-records.md new file mode 100644 index 0000000..9eff06f --- /dev/null +++ b/workplans/WHITEHAT-WP-0002-receipt-admission-and-abort-records.md @@ -0,0 +1,65 @@ +--- +id: WHITEHAT-WP-0002 +type: workplan +title: "Receipt admission example, CLI coverage, and abort records" +domain: infotech +repo: whitehat-security +status: finished +owner: net-kingdom +topic_slug: whitehat-security +created: "2026-08-22" +updated: "2026-08-22" +--- + +# WHITEHAT-WP-0002 — receipt admission and abort records + +## Goal + +Close the gap that aborted `WH-ENG-20260822-AUDIT-E2-02`: operators need a +checked-in value-safe receipt shape, a CLI path that issues a plane lease +from it, and a way to record an abort without calling it target evidence. + +This plan does **not** authorize a live trial. The next E2 run remains a new +engagement under WHITEHAT-WP-0001. + +## Tasks + +### T01 — Example projection receipt + +```task +id: WHITEHAT-WP-0002-T01 +status: done +priority: high +``` + +Checked-in example with no secret material, matching +`schemas/projection-receipt.schema.json`. Operators copy it at window start +instead of inventing a shape under the three-minute mint gate. + +### T02 — CLI coverage for `admit-plane --receipt` + +```task +id: WHITEHAT-WP-0002-T02 +status: done +priority: high +``` + +Prove the CLI admits from a valid receipt, refuses secret material, and still +fails closed without `--receipt`. + +### T03 — Abort records are not target assurance + +```task +id: WHITEHAT-WP-0002-T03 +status: done +priority: medium +``` + +Allow `evidence_class: abort` so a zero-packet abort can be queued without +being a Tenancy Posture artifact. Record `WH-ENG-20260822-AUDIT-E2-02` that +way. + +## Sequencing + +T01 and T02 can proceed together. T03 is independent. None of this is a live +run.