From 8a97fac17f00f0f173f41a10a5b89885f16fbdfe Mon Sep 17 00:00:00 2001 From: tegwick Date: Sat, 22 Aug 2026 00:05:15 +0200 Subject: [PATCH] Correct audit engagement and idempotent fixture setup Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a0260c-4067-7052-9647-ad000d576e38 --- engagements/2026-08-21-audit-core-e2.json | 3 ++- engagements/2026-08-21-e2-proposals.md | 5 +++-- runners/audit_core_e2.py | 22 +++++++++++++++------- src/whitehat_security/engagement.py | 2 ++ tests/test_audit_core_runner.py | 12 ++++++++++++ 5 files changed, 34 insertions(+), 10 deletions(-) diff --git a/engagements/2026-08-21-audit-core-e2.json b/engagements/2026-08-21-audit-core-e2.json index f772d61..6449db9 100644 --- a/engagements/2026-08-21-audit-core-e2.json +++ b/engagements/2026-08-21-audit-core-e2.json @@ -6,7 +6,8 @@ "expires_at": "2026-08-21T23:15:00Z", "target": "http://audit-core.audit-core.svc.cluster.local:8080", "target_owner": "audit-core / infrastructure operator", - "target_owner_acknowledged_at": "2026-08-21T21:56:00Z", + "target_owner_acknowledged_at": null, + "target_owner_acknowledgement_status": "pending corrected adapter/record review; target review received 2026-08-21T22:04:19Z", "environment": "production", "production_approval": "Explicit user approval of engagements/2026-08-21-e2-proposals.md in the coordinating session", "namespace": "audit-core", diff --git a/engagements/2026-08-21-e2-proposals.md b/engagements/2026-08-21-e2-proposals.md index 84bca41..1523cf6 100644 --- a/engagements/2026-08-21-e2-proposals.md +++ b/engagements/2026-08-21-e2-proposals.md @@ -53,8 +53,9 @@ invented tenant claim is not acceptable evidence. ## Explicitly excluded -These E2 approvals do not cover direct PostgreSQL access, RLS probes, schema or -role changes, connection/load saturation, recovery, production, adjacent +These E2 approvals cover only the two exact named production deployments and +do not cover any other production target, direct PostgreSQL access, RLS +probes, schema or role changes, connection/load saturation, recovery, adjacent services, real tenant identifiers, or collection of response bodies. E3 and P1/P2 each receive a separate approval package after E2 establishes the safe execution path. diff --git a/runners/audit_core_e2.py b/runners/audit_core_e2.py index b691a3d..d13be74 100644 --- a/runners/audit_core_e2.py +++ b/runners/audit_core_e2.py @@ -80,12 +80,8 @@ class Client: self.args.base_url + path, data=raw, method=method, - headers={ - "Authorization": f"Bearer {token}", - "Content-Type": "application/json", - "User-Agent": f"whitehat-security/{self.args.engagement_id}", - "X-Correlation-ID": self.args.correlation, - }, + headers=request_headers(token, self.args.engagement_id, + self.args.correlation, payload), ) started = time.monotonic() try: @@ -141,6 +137,19 @@ def _p95(samples: list[float]) -> float: return ordered[max(0, math.ceil(len(ordered) * .95) - 1)] +def request_headers(token: str, engagement_id: str, correlation: str, + payload: dict[str, Any] | None) -> dict[str, str]: + headers = { + "Authorization": f"Bearer {token}", + "Content-Type": "application/json", + "User-Agent": f"whitehat-security/{engagement_id}", + "X-Correlation-ID": correlation, + } + if payload is not None: + headers["Idempotency-Key"] = str(payload["id"]) + return headers + + def event(event_id: str, tenant: str, correlation: str) -> dict[str, Any]: return { "id": event_id, @@ -276,4 +285,3 @@ def main() -> None: if __name__ == "__main__": main() - diff --git a/src/whitehat_security/engagement.py b/src/whitehat_security/engagement.py index 31667c4..73f7a0e 100644 --- a/src/whitehat_security/engagement.py +++ b/src/whitehat_security/engagement.py @@ -42,6 +42,8 @@ class Engagement: end = _timestamp(self.raw["window_end"]) expiry = _timestamp(self.raw["expires_at"]) approved = _timestamp(self.raw["approved_at"]) + if not self.raw["target_owner_acknowledged_at"]: + raise AuthorizationError("target-owner acknowledgement is pending") acknowledged = _timestamp(self.raw["target_owner_acknowledged_at"]) if not approved <= current <= min(end, expiry): raise AuthorizationError("engagement is outside its approved time/expiry window") diff --git a/tests/test_audit_core_runner.py b/tests/test_audit_core_runner.py index 5cc8eca..30cc2f8 100644 --- a/tests/test_audit_core_runner.py +++ b/tests/test_audit_core_runner.py @@ -30,3 +30,15 @@ def test_event_is_synthetic_and_correlation_bound(): def test_p95_is_conservative_for_small_runs(): assert runner._p95([10, 20, 30]) == 30 + +def test_post_headers_bind_idempotency_key_to_event_id(): + headers = runner.request_headers( + "secret-not-rendered", "eng-1", "corr-1", {"id": "event-1"} + ) + assert headers["Idempotency-Key"] == "event-1" + assert headers["User-Agent"] == "whitehat-security/eng-1" + + +def test_get_headers_have_no_idempotency_key(): + headers = runner.request_headers("secret", "eng-1", "corr-1", None) + assert "Idempotency-Key" not in headers