diff --git a/SCOPE.md b/SCOPE.md index 5c88ab6..f1bf006 100644 --- a/SCOPE.md +++ b/SCOPE.md @@ -69,13 +69,13 @@ boundary always holds. - `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`. -- `T03` is in progress: `WH-ENG-20260822-AUDIT-E2-01` is operator-approved - and owner-acknowledged. The window has not started. Custody projection is - scheduled for `18:00Z`; tokens must not be minted early. +- `T03` is in progress: `WH-ENG-20260822-AUDIT-E2-01` expired at 18:15Z with + no live run. A new engagement ID is required for a retry. - `T04` is complete for every applicable E2 probe: generic and audit-core shaped fixtures fail known-bad and pass known-good in-process. - `T05` is in progress: 24-hour cadence, offline evaluator and in-process - calibration exist; `platform-pg` is pending a live window. + calibration exist; `platform-pg` is `not_applicable` for the requested + runtime identity. - `T06` is in progress: the characterization evaluator is calibrated in-process; `shared-substrate` is pending a live window and ceiling. - `T07` is in progress: reports can be queued to `outbox/`; the first target diff --git a/docs/test-plane.md b/docs/test-plane.md index 8c3a154..35c462d 100644 --- a/docs/test-plane.md +++ b/docs/test-plane.md @@ -47,7 +47,7 @@ Create `plane/KILL` to abort independently of the runner process. ## Retired identifiers -`WH-ENG-20260821-AUDIT-E2` and `WH-ENG-20260821-TENANT-E2` are terminal. -`WH-ENG-20260822-AUDIT-E2-01` is operator-approved and owner-acknowledged. -The window starts at `2026-08-22T18:00:00Z`. Do not mint the 900s identities -before then. The live custody broker remains unconnected until projection. +`WH-ENG-20260821-AUDIT-E2`, `WH-ENG-20260821-TENANT-E2`, and +`WH-ENG-20260822-AUDIT-E2-01` are terminal. The last of those expired at +18:15Z with no projection-ready notice and zero packets. A later live E2 run +needs a new ID, a new window, and a fresh attended projection. diff --git a/engagements/2026-08-22-audit-core-e2.json b/engagements/2026-08-22-audit-core-e2.json index 060abac..e038208 100644 --- a/engagements/2026-08-22-audit-core-e2.json +++ b/engagements/2026-08-22-audit-core-e2.json @@ -1,6 +1,8 @@ { "engagement_id": "WH-ENG-20260822-AUDIT-E2-01", - "status": "approved", + "status": "expired", + "expired_at": "2026-08-22T18:15:00Z", + "expiry_reason": "Approved window ended with no projection-ready notice from railiance-platform and zero target requests. Do not reuse this identifier.", "proposal_at": "2026-08-22T09:44:00Z", "proposal_reason": "Fresh live-e2 proposal after foundational plane apply. Do not reuse WH-ENG-20260821-AUDIT-E2.", "authorization_id": "operator-session-2026-08-22-e2-approval", diff --git a/engagements/2026-08-22-audit-core-e2.md b/engagements/2026-08-22-audit-core-e2.md index d432432..dbf9ea0 100644 --- a/engagements/2026-08-22-audit-core-e2.md +++ b/engagements/2026-08-22-audit-core-e2.md @@ -1,8 +1,8 @@ # WH-ENG-20260822-AUDIT-E2-01 -Status: **operator-approved and target-owner-acknowledged**. The window has -not started. Credentials must not be minted until window start so the 900s -TTL covers the run and cleanup. +Status: **expired**. The approved window ended at `2026-08-22T18:15:00Z` with +no projection-ready notice and zero target requests. This identifier is +terminal and must not be reused. | Gate | State | | --- | --- | @@ -11,12 +11,12 @@ TTL covers the run and cleanup. | Foundational plane | applied by ops-mason `c26a6e5` | | Window | `2026-08-22T18:00:00Z`–`18:15:00Z` | | Image | `sha256:c2fe39a0185b99be3fc0cb14d2de69772b8e66e20490097c9d11d90cc39719a6` | -| Custody projection | scheduled, not minted | +| Custody projection | never minted; no projection-ready notice arrived | | Runner pod | not applied | +| Packets | none | -Offline preflight now fails closed with `engagement window has not started`. -At `18:00Z`, `Engagement.load` authorizes the record; `admit-plane` still -fails closed until the live custody broker is connected. +Offline preflight fails closed with `engagement window elapsed with no live +run`. A retry needs a new identifier. Invocation, including required `--forged-event whitehat-e2-forged-b-20260822`: `engagements/runtime/WH-ENG-20260822-AUDIT-E2-01-invocation.md`. diff --git a/engagements/README.md b/engagements/README.md index 64a29d8..49c6ca0 100644 --- a/engagements/README.md +++ b/engagements/README.md @@ -14,7 +14,6 @@ from `whitehat admit-plane`. `WH-ENG-20260821-AUDIT-E2` and `WH-ENG-20260821-TENANT-E2` are cancelled and must not be reused. The pod manifest under `runtime/` is bound to a cancelled ID and must not be applied. -`WH-ENG-20260822-AUDIT-E2-01` is operator-approved and owner-acknowledged. -Offline validation currently fails closed because the window has not started. -Do not mint the 900s identities until `2026-08-22T18:00:00Z`. +`WH-ENG-20260822-AUDIT-E2-01` expired at `2026-08-22T18:15:00Z` with no live +run. Do not reuse it. A later attempt needs a new identifier and window. diff --git a/engagements/runtime/WH-ENG-20260822-AUDIT-E2-01-invocation.md b/engagements/runtime/WH-ENG-20260822-AUDIT-E2-01-invocation.md index e4b4db5..b8a6efc 100644 --- a/engagements/runtime/WH-ENG-20260822-AUDIT-E2-01-invocation.md +++ b/engagements/runtime/WH-ENG-20260822-AUDIT-E2-01-invocation.md @@ -1,8 +1,7 @@ # WH-ENG-20260822-AUDIT-E2-01 — invocation (no secrets) -Do not apply the runner pod and do not mint credentials until the custody -owner projects two mount-only tokens at window start. Minting now would -exhaust the 900-second TTL before `18:00Z`. +This window elapsed at `2026-08-22T18:15:00Z` with no projection and no +packets. Do not apply the runner or mint credentials against this identifier. Current time-gate: `validate-engagement` and `admit-plane` fail closed with `engagement window has not started` until `2026-08-22T18:00:00Z`. After the diff --git a/plane/README.md b/plane/README.md index 618cec2..1c86c79 100644 --- a/plane/README.md +++ b/plane/README.md @@ -2,8 +2,9 @@ This repository does not apply cluster objects, request credentials, or send packets. `ops-mason` provisions the foundational plane from these files -**before** a fresh engagement is proposed. Cancelled IDs -`WH-ENG-20260821-AUDIT-E2` and `WH-ENG-20260821-TENANT-E2` are terminal. +**before** a fresh engagement is proposed. Cancelled or expired IDs +`WH-ENG-20260821-AUDIT-E2`, `WH-ENG-20260821-TENANT-E2`, and +`WH-ENG-20260822-AUDIT-E2-01` are terminal. ## Ordering diff --git a/src/whitehat_security/engagement.py b/src/whitehat_security/engagement.py index ff6ca59..ee319a9 100644 --- a/src/whitehat_security/engagement.py +++ b/src/whitehat_security/engagement.py @@ -39,6 +39,10 @@ class Engagement: def validate(self, *, now: datetime | None = None) -> None: if self.raw.get("status") == "cancelled": raise AuthorizationError("engagement is cancelled") + if self.raw.get("status") == "expired": + raise AuthorizationError( + "engagement window elapsed with no live run; identifier must not be reused" + ) if self.raw.get("status") == "proposed": raise AuthorizationError( "engagement is proposed; operator approval and owner acknowledgement are pending" diff --git a/src/whitehat_security/plane.py b/src/whitehat_security/plane.py index 7f73743..f0dabfc 100644 --- a/src/whitehat_security/plane.py +++ b/src/whitehat_security/plane.py @@ -15,6 +15,7 @@ STANDING_E2_TTL = 900 TERMINAL_IDS = frozenset({ "WH-ENG-20260821-AUDIT-E2", "WH-ENG-20260821-TENANT-E2", + "WH-ENG-20260822-AUDIT-E2-01", }) APPROVAL_CLASSES = { "fixture-e2": frozenset({"e2-differential"}), @@ -41,7 +42,7 @@ def retired_ids(engagements_dir: str | Path | None = None) -> set[str]: data = json.loads(path.read_text(encoding="utf-8")) except (OSError, json.JSONDecodeError, ValueError): continue - if data.get("status") == "cancelled" and data.get("engagement_id"): + if data.get("status") in {"cancelled", "expired"} and data.get("engagement_id"): retired.add(data["engagement_id"]) return retired diff --git a/targets/README.md b/targets/README.md index 2a50abc..5c0dce7 100644 --- a/targets/README.md +++ b/targets/README.md @@ -10,7 +10,7 @@ honest applicability record the test plane admits against. | `tenant-engine` | not_applicable | No tenant-A identity without tenant-B authority. | | `flex-auth` | pending | Initial envelope; owner has not confirmed the attacker identity. | | `fixture-e3` | applicable | In-process E3 evaluator. One runtime identity, no `BYPASSRLS`. | -| `platform-pg` | pending | Live E3 needs a named database, ordinary runtime role, and dated window. | +| `platform-pg` | not_applicable | No ordinary runtime identity can read the conformance view. | | `fixture-capacity` | applicable | In-process P1/P2 evaluator. Generates no load. | | `shared-substrate` | pending | Live capacity needs an operator window and aggressor ceiling. | diff --git a/targets/platform-pg-e3.json b/targets/platform-pg-e3.json index ed4fc56..c11528b 100644 --- a/targets/platform-pg-e3.json +++ b/targets/platform-pg-e3.json @@ -3,8 +3,8 @@ "target_id": "platform-pg", "posture_claim": "E3", "attacker_model": "E3-A omitted-predicate accident; E3-B documented SQL-compromise limit", - "applicability": "pending", - "applicability_reason": "Named in the rules of engagement as outside the initial API-probe envelope. A live run needs the database owner to confirm an ordinary runtime role without BYPASSRLS, the conformance view, and a dated window. This registration does not authorize a connection.", + "applicability": "not_applicable", + "applicability_reason": "rapp-postgres 2026-08-22: the only E3 apparatus consumer is isolation-probe. Ordinary runtime group isolation_probe_app is NOBYPASSRLS, but SELECT on isolation_probe.rls_conformance is granted only to isolation_probe_migrate. No live workload has enabled the template. An ordinary runtime identity that can exercise the conformance view does not exist. Do not open a database connection.", "approval_classes": ["e3"], "routes": [], "identities": { diff --git a/tests/test_cli.py b/tests/test_cli.py index af740cb..ecb2570 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -24,16 +24,16 @@ def test_kill_switch_is_clear_by_default(capsys): assert capsys.readouterr().out.strip() == "clear" -def test_validate_engagement_refuses_before_window(capsys): +def test_validate_engagement_refuses_elapsed_window(capsys): with pytest.raises(SystemExit) as stopped: main(["validate-engagement", "engagements/2026-08-22-audit-core-e2.json"]) assert stopped.value.code == 2 err = capsys.readouterr().err assert err.startswith("not authorized:") - assert "engagement window has not started" in err + assert "elapsed" in err -def test_admit_plane_refuses_before_window(capsys): +def test_admit_plane_refuses_elapsed_window(capsys): with pytest.raises(SystemExit) as stopped: main([ "admit-plane", @@ -43,7 +43,7 @@ def test_admit_plane_refuses_before_window(capsys): assert stopped.value.code == 2 err = capsys.readouterr().err assert "not authorized:" in err - assert "engagement window has not started" in err + assert "elapsed" in err def test_admit_plane_refuses_cancelled_engagement(capsys): diff --git a/tests/test_engagement.py b/tests/test_engagement.py index ee2e767..c162223 100644 --- a/tests/test_engagement.py +++ b/tests/test_engagement.py @@ -63,6 +63,13 @@ def test_pending_owner_acknowledgement_fails_closed(tmp_path): load(tmp_path, data) +def test_elapsed_record_fails_closed_even_inside_old_window(tmp_path): + data = record() + data["status"] = "expired" + with pytest.raises(AuthorizationError, match="elapsed"): + load(tmp_path, data) + + def test_proposed_record_fails_closed_before_window_checks(tmp_path): data = record() data["status"] = "proposed" diff --git a/tests/test_plane.py b/tests/test_plane.py index 004ef5f..bfcaf7b 100644 --- a/tests/test_plane.py +++ b/tests/test_plane.py @@ -62,7 +62,7 @@ def test_catalog_loads_honest_applicability(): assert catalog["flex-auth"]["applicability"] == "pending" assert catalog["fixture-e3"]["applicability"] == "applicable" assert catalog["fixture-capacity"]["applicability"] == "applicable" - assert catalog["platform-pg"]["applicability"] == "pending" + assert catalog["platform-pg"]["applicability"] == "not_applicable" assert catalog["shared-substrate"]["applicability"] == "pending" @@ -70,6 +70,7 @@ def test_retired_ids_include_cancelled_records(): ids = retired_ids() assert "WH-ENG-20260821-AUDIT-E2" in ids assert "WH-ENG-20260821-TENANT-E2" in ids + assert "WH-ENG-20260822-AUDIT-E2-01" in ids def test_fixture_plane_admits_and_projects_handles_without_secrets(tmp_path): @@ -207,14 +208,14 @@ def test_fixture_capacity_projects_no_identities(tmp_path): assert cleanup(lease, broker)["credential_revocation"] == "revoked" -def test_pending_e3_target_is_refused(tmp_path): +def test_not_applicable_e3_target_is_refused(tmp_path): engagement = load_engagement(tmp_path, fixture_record( target_id="platform-pg", approval_class="e3", techniques=["e3-rls"], environment="build", plane_namespace="whitehat", runner_image_digest="sha256:abc", database="platform-pg", routes=["conformance"], )) - with pytest.raises(AuthorizationError, match="pending"): + with pytest.raises(AuthorizationError, match="not_applicable"): admit( engagement=engagement, registration=load_registration("targets/platform-pg-e3.json"), diff --git a/workplans/WHITEHAT-WP-0001-cross-tenant-evidence.md b/workplans/WHITEHAT-WP-0001-cross-tenant-evidence.md index 48b66f4..05c77a7 100644 --- a/workplans/WHITEHAT-WP-0001-cross-tenant-evidence.md +++ b/workplans/WHITEHAT-WP-0001-cross-tenant-evidence.md @@ -162,9 +162,10 @@ target request and are retired. ops-mason applied the foundational plane on 2026-08-22. The operator approved `WH-ENG-20260822-AUDIT-E2-01` at 2026-08-22T09:52:55Z; audit-core acknowledged at 2026-08-22T10:04:56Z (`32afceb`) after the fixture/digest correction `1418dac`. Offline preflight -now fails closed because the window has not started. Custody must project two -≤900s identities at `18:00Z`, not earlier. Whitehat will not relabel -tenant-engine to finish this task. +recorded a window that elapsed at 18:15Z with no projection-ready notice and +zero packets. That identifier is expired and must not be reused. A later live +E2 run needs a new ID, a new window, and a fresh attended projection. +Whitehat will not relabel tenant-engine to finish this task. ### T04 — Prove the probes fail @@ -223,8 +224,9 @@ and reporting latency, with event-triggered pre-promotion runs after schema, role, RLS or security-definer changes. `src/whitehat_security/e3.py` encodes the seven expected outcomes, keeps the SQL-compromise GUC reset labeled as E3's documented limit, and calibrates known-good/known-bad in-process. `platform-pg` -is registered `pending`. A live database run still requires its separate -authorized target, named database, ordinary runtime role, and window. +is `not_applicable` for an ordinary runtime conformance-view identity +(rapp-postgres, 2026-08-22). A live database run still requires a separately +reviewed runtime-safe surface, named database, and window. ### T06 — Noisy-neighbour characterisation (the P1/P2 artifact)