From 6aacebfa298265da42e3a99f43010e37380250d0 Mon Sep 17 00:00:00 2001 From: ECHO OMEGA PRIME Date: Tue, 10 Nov 2026 22:21:02 -0600 Subject: [PATCH 1/2] fix(subscriber): redact untrusted dispatch errors from immutable orphan audit --- src/echo_certification_forge/subscriber.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/echo_certification_forge/subscriber.py b/src/echo_certification_forge/subscriber.py index 6a6df86..afa263a 100644 --- a/src/echo_certification_forge/subscriber.py +++ b/src/echo_certification_forge/subscriber.py @@ -4203,7 +4203,18 @@ def _recover_expired_dispatches( outcome="allowed", details={ "attempts": int(row["attempts"]), - "last_error": row["last_error"], + # Dispatch error text may originate from a worker or remote failure. + # Audit entries are immutable: retain only known machine reason codes, + # never an unbounded/raw exception or message that might include secrets. + "last_error": ( + row["last_error"] + if row["last_error"] in ( + "subscriber_worker_claim_denied", + "dispatch_attempts_exhausted", + "dispatch_lease_expired", + ) + else "redacted" if row["last_error"] else None + ), "meter_compensated": True, }, ) From 24277990381d376ab427e42851c62a03a3110a34 Mon Sep 17 00:00:00 2001 From: ECHO OMEGA PRIME Date: Tue, 10 Nov 2026 22:21:28 -0600 Subject: [PATCH 2/2] test(subscriber): prove orphan audits cannot persist untrusted worker error text --- tests/test_dispatch_orphan_audit_redaction.py | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 tests/test_dispatch_orphan_audit_redaction.py diff --git a/tests/test_dispatch_orphan_audit_redaction.py b/tests/test_dispatch_orphan_audit_redaction.py new file mode 100644 index 0000000..db925e9 --- /dev/null +++ b/tests/test_dispatch_orphan_audit_redaction.py @@ -0,0 +1,58 @@ +"""The immutable orphan-reconciliation audit must not persist untrusted error text. + +All state changes use pytest's private temporary SQLite database. +This test covers the recovery path only, without a production dispatcher. +""" +from __future__ import annotations + +import json +import sqlite3 + +import pytest + +from echo_certification_forge.models import RunState +from test_dispatch_orphaned_runs import _orphan_the_old_way, _run, _usage + + +@pytest.mark.parametrize( + ("incoming_error", "audited_error"), + [ + ("subscriber_worker_claim_denied", "subscriber_worker_claim_denied"), + ("Authorization: Bearer synthetic-do-not-store-000", "redacted"), + (None, None), + ], +) +def test_orphan_recovery_audit_redacts_untrusted_last_error( + tmp_path, manifest, incoming_error, audited_error +): + store, governance, org, run_id = _run( + tmp_path, manifest, "audit-redaction", max_attempts=3 + ) + _orphan_the_old_way(governance, org, run_id) + with sqlite3.connect(governance.db_path) as connection: + connection.execute( + "UPDATE subscriber_run_dispatches SET last_error = ? " + "WHERE run_id = ? AND organization_id = ?", + (incoming_error, run_id, org.organization_id), + ) + connection.commit() + + assert governance.claim_dispatch( + "audit-recovery", organization_id=org.organization_id + ) is None + assert store.get_run(run_id, org.organization_id)["state"] == ( + RunState.INFRASTRUCTURE_FAILURE.value + ) + assert _usage(governance, org)["meters"]["certification_runs"] == 0 + + with sqlite3.connect(governance.db_path) as connection: + audit_rows = connection.execute( + "SELECT details_json FROM subscriber_audit_events " + "WHERE organization_id = ? AND resource_id = ? " + "AND action = 'certification.dispatch_orphan_reconciled'", + (org.organization_id, run_id), + ).fetchall() + assert len(audit_rows) == 1 + recorded = audit_rows[0][0] + assert json.loads(recorded)["last_error"] == audited_error + assert "synthetic-do-not-store-000" not in recorded