What invariants preserve audit trail integrity when the auditing system is compromised?
**Date of Record:** 2026 October 27 **Scope:** Applies when an agentic session relies on an audit trail (audit logs, tamper-evident records, or independent auditors) whose integrity must be verified, and the audit trail itself or the auditing system has been compromised—records may be modified, deleted, or fabricated. Does not apply to sessions with cryptographically immutable audit trails or sessions that do not rely on audit.
Core Distinction
The separation between **audit records** (the trail of events) and **audit integrity** (the trail's tamper-evidence). Relying on records creates the **Audit Corruption**: the audit trail's integrity mechanism has failed, and the records can no longer be distinguished as truthful vs. tampered—even records that appear intact may have been selectively modified, and the corruption is invisible from the records alone.
What Was Observed
In audit-dependent agent architectures, failing to bound audit compromise produces three primary failure modes:
- **Retroactive Modification:** Audit records are altered after the events they describe. The session treats the modified records as authoritative history.
- **Selective Deletion:** Records describing specific events are deleted. The session's view of history has gaps that appear benign.
- **Fabrication Injection:** Records describing events that did not occur are added. The session's view of history contains events that did not happen.
Recorded Elements
- **`audit-integrity-verification-receipt`:** The exact integrity check on the audit trail (cryptographic chain, Merkle proof, or independent witness), so integrity is measured not assumed.
- **`audit-alternative-witness-signature`:** For compromised audits, independent witnesses (peer logs, external timestamps, or cryptographic commitments) that can corroborate or contradict specific records.
- **`audit-suspect-scope-receipt`:** The exact scope of suspected corruption (which records, which time windows), so reconstruction is targeted.
- **`audit-recovery-path-signature`:** The exact reconstruction path (cross-reference with witnesses, re-execute events, or declare records unreliable), so integrity is recoverable where possible.
Condition of Validity
* **Applies when:** The session relies on an audit trail, the audit trail can be compromised, and downstream decisions depend on audit integrity. * **Does not apply when:** The audit trail is cryptographically immutable, the audit is not relied on, or the trail is trivially re-derivable.
Decision Boundary
- **Is the session relying on an audit trail?** If no, standard operation applies. If yes, proceed.
- **Does `audit-integrity-verification-receipt` confirm integrity?** If yes, records are trustworthy. If no, records are suspect.
- **Are `audit-alternative-witness-signature`s available?** If yes, records can be cross-verified. If no, records are isolated.
- **Conversely, when can a session accept audit records without integrity verification?** Only when records are cryptographically committed at write time (content-addressed, signed by writer), or the records are corroborated by independent witnesses for every critical claim.
Operational Effect
- **Retroactive Modification Detection:** Integrity checks detect post-hoc changes.
- **Selective Deletion Awareness:** Witness cross-references surface gaps.
- **Fabrication Detection:** Independent corroboration distinguishes genuine from fabricated.
from dataclasses import dataclass
from enum import Enum, auto
import hashlib
import json
class AuditVerdict(Enum):
INTEGRITY_VERIFIED = auto()
INTEGRITY_SUSPECT = auto()
ALTERNATIVE_VERIFIED = auto()
UNRECOVERABLE_CORRUPTION = auto()
@dataclass(frozen=True)
class AuditTrail:
audit_id: str
integrity_verified: bool
compromised_detected: bool
alternative_witnesses: int
suspect_scope_defined: bool
@dataclass(frozen=True)
class AuditPolicy:
min_witnesses_for_alternative: int
@dataclass(frozen=True)
class AuditManifest:
verdict: AuditVerdict
directive: str
manifest_digest: str
class AuditIntegrityGuard:
@staticmethod
def _hash(data: str) -> str:
return hashlib.sha256(data.encode("utf-8")).hexdigest()
@classmethod
def evaluate_trail(
cls, trail: AuditTrail, policy: AuditPolicy
) -> AuditManifest:
# Gate 1: Verified
if trail.integrity_verified:
digest = cls._hash(json.dumps({
"audit": trail.audit_id,
"verified": True
}, sort_keys=True))
return AuditManifest(
verdict=AuditVerdict.INTEGRITY_VERIFIED,
directive=f"PROCEED: Audit {trail.audit_id} integrity verified.",
manifest_digest=digest
)
# Gate 2: Alternative witnesses
if trail.alternative_witnesses >= policy.min_witnesses_for_alternative:
digest = cls._hash(json.dumps({
"audit": trail.audit_id,
"witnesses": str(trail.alternative_witnesses)
}, sort_keys=True))
return AuditManifest(
verdict=AuditVerdict.ALTERNATIVE_VERIFIED,
directive=f"PROCEED_WITH_WITNESSES: Audit {trail.audit_id} corroborated by {trail.alternative_witnesses}.",
manifest_digest=digest
)
# Gate 3: Suspect scope
if trail.compromised_detected:
if not trail.suspect_scope_defined:
return AuditManifest(
verdict=AuditVerdict.UNRECOVERABLE_CORRUPTION,
directive=f"HALT: Audit {trail.audit_id} compromised with no scope defined.",
manifest_digest=cls._hash(f"NO_SCOPE:{trail.audit_id}")
)
return AuditManifest(
verdict=AuditVerdict.INTEGRITY_SUSPECT,
directive=f"FLAG: Audit {trail.audit_id} compromised, scope bounded.",
manifest_digest=cls._hash(f"SUSPECT:{trail.audit_id}")
)
# Gate 4: No verification
return AuditManifest(
verdict=AuditVerdict.INTEGRITY_SUSPECT,
directive=f"FLAG: Audit {trail.audit_id} not verified.",
manifest_digest=cls._hash(f"UNVERIFIED:{trail.audit_id}")
)