Witness — Hash-Only Remote Witnesses
::: enforcecore.auditor.witness
Overview
A witness receives the hash of each audit entry as it is written, enabling tamper detection even if an attacker rebuilds the Merkle chain. Because the witness stores only hashes (~200 bytes per entry), the data overhead is negligible.
This design was directly informed by guidance from Prof. Dan S. Wallach (Rice University), co-author of Crosby & Wallach (2009).
Quick Start
from enforcecore.auditor import Auditor
from enforcecore.auditor.witness import CallbackWitness, verify_with_witness
# Collect witness hashes
hashes = []
witness = CallbackWitness(hashes.append)
auditor = Auditor(output_path="audit.jsonl", witness=witness)
# Record some entries
auditor.record(tool_name="search", policy_name="strict")
auditor.record(tool_name="calculator", policy_name="strict")
# Verify trail against witness
result = verify_with_witness("audit.jsonl", witness_hashes=[r.entry_hash for r in hashes])
assert result.is_validVia Environment Variables
export ENFORCECORE_AUDIT_WITNESS_FILE=/var/log/ec-witness.jsonlThis creates a FileWitness automatically when using @enforce().
Backends
CallbackWitness
Route witness hashes to any callable — queue, HTTP endpoint, database.
from enforcecore.auditor.witness import CallbackWitness
# In-memory collection
records = []
witness = CallbackWitness(records.append)
# Message queue
witness = CallbackWitness(my_queue.put)!!! warning "Blocking I/O"
The callback is invoked synchronously on every record() call.
If it performs network I/O (HTTP POST), it adds that latency to every
audit entry. Wrap slow callbacks in a queue-based pattern for
production use.
FileWitness
Append witness hashes to a separate JSONL file. Best when the witness file is on a different volume or owned by a different user.
from enforcecore.auditor.witness import FileWitness
witness = FileWitness("/var/log/ec-witness.jsonl")LogWitness
Emit witness hashes via Python logging — routes to syslog, journald,
or any configured log handler.
from enforcecore.auditor.witness import LogWitness
witness = LogWitness() # uses "enforcecore.witness" loggerVerification
verify_with_witness() cross-checks audit trail hashes against witness
records. If an attacker rebuilt the Merkle chain, the hashes will differ.
from enforcecore.auditor.witness import verify_with_witness
# From a list of hashes
result = verify_with_witness("audit.jsonl", witness_hashes=hash_list)
# From a FileWitness file
result = verify_with_witness("audit.jsonl", witness_file="/var/log/ec-witness.jsonl")
if not result.is_valid:
print(f"TAMPER DETECTED: {result.errors}")Classes
| Class | Description |
|---|---|
WitnessRecord |
Frozen dataclass: entry_id, entry_hash, previous_hash, sequence, timestamp |
WitnessBackend |
ABC — implement publish(record) and close() |
CallbackWitness |
Sends records to a callable |
FileWitness |
Appends records to JSONL file |
LogWitness |
Emits records via Python logging |
WitnessVerificationResult |
Verification result: is_valid, matched, mismatched, errors |
Functions
| Function | Description |
|---|---|
verify_with_witness(trail_path, *, witness_hashes=, witness_file=) |
Cross-check trail against witness |