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
This commit is contained in:
tegwick 2026-08-22 21:40:33 +02:00
parent 45548e44a2
commit 481ed6add5
11 changed files with 247 additions and 5 deletions

View file

@ -65,7 +65,7 @@ boundary always holds.
## Current state ## Current state
- Repository status: active. - 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. - `T01` is complete: the rules of engagement were accepted on 2026-08-21.
- `T02` is complete: the per-axis attacker model is recorded in - `T02` is complete: the per-axis attacker model is recorded in
`docs/attacker-model.md`. `docs/attacker-model.md`.

View file

@ -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/<record>.json targets/audit-core-e2.json \
--receipt engagements/receipts/<engagement>.json
```
Without `--receipt`, live admission fails closed and requests no credential.

View file

@ -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
}

View file

@ -3,7 +3,8 @@
This directory stores sanitized run artifacts. `offline-calibration.json` and This directory stores sanitized run artifacts. `offline-calibration.json` and
`offline-e3-calibration.json` are generated from repository-created fixtures `offline-e3-calibration.json` are generated from repository-created fixtures
and prove only that the harness distinguishes known-good from known-bad 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, Before committing target evidence, verify that it contains no response body,
credential, database URL, real tenant identifier, or real tenant value. A credential, database URL, real tenant identifier, or real tenant value. A

View file

@ -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."
}

View file

@ -11,7 +11,7 @@
], ],
"properties": { "properties": {
"schema_version": {"const": "whitehat-run/v1"}, "schema_version": {"const": "whitehat-run/v1"},
"evidence_class": {"enum": ["fixture", "target"]}, "evidence_class": {"enum": ["fixture", "target", "abort"]},
"outcome": {"enum": ["pass", "finding", "inconclusive", "aborted"]}, "outcome": {"enum": ["pass", "finding", "inconclusive", "aborted"]},
"attempted_operations": {"type": "integer", "minimum": 0}, "attempted_operations": {"type": "integer", "minimum": 0},
"probes": {"type": "array"}, "probes": {"type": "array"},

View file

@ -102,7 +102,7 @@ class ProbeResult:
class RunReport: class RunReport:
schema_version: str schema_version: str
run_id: str run_id: str
evidence_class: Literal["fixture", "target"] evidence_class: Literal["fixture", "target", "abort"]
engagement_id: str engagement_id: str
authorization_id: str authorization_id: str
target: str target: str

View file

@ -28,13 +28,17 @@ def risk_nexus_message(report: RunReport) -> str:
] ]
if report.outcome == "finding": if report.outcome == "finding":
lines.extend(["", "Reporter supplies facts only; risk-nexus owns severity and disclosure."]) 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" return "\n".join(lines) + "\n"
def queue_risk_nexus(report: RunReport, outbox: str | Path) -> Path: def queue_risk_nexus(report: RunReport, outbox: str | Path) -> Path:
"""Persist a delivery artifact. Fixture calibration is not target assurance.""" """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") 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 = Path(outbox)
directory.mkdir(parents=True, exist_ok=True) directory.mkdir(parents=True, exist_ok=True)
path = directory / f"{report.run_id}.md" path = directory / f"{report.run_id}.md"

View file

@ -1,8 +1,10 @@
import json import json
from pathlib import Path
import pytest import pytest
from whitehat_security.cli import main from whitehat_security.cli import main
from whitehat_security.plane import ReceiptBroker
def test_validate_engagement_reports_clean_denial(tmp_path, capsys): 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 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): def test_deliver_refuses_fixture_calibration(tmp_path, capsys):
report = tmp_path / "fixture.json" report = tmp_path / "fixture.json"
report.write_text(json.dumps({ report.write_text(json.dumps({

View file

@ -48,3 +48,18 @@ def test_risk_message_contains_pass_and_no_severity():
assert "**pass**" in message assert "**pass**" in message
assert "Severity" not in message assert "Severity" not in message
assert "not proof" 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

View file

@ -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.