Forensics-grade evidence handling ISO/IEC 27037 + NIST SP 800-86 chain of custody, hash-chained evidence DB, PQC signatures, classification + TLP markings.
The four pillars
Integrity
SHA-256 hash chain across the evidence DB. Append-only rows. Any modification cascades a chain-hash mismatch detectable by digger verify.
Authenticity
Optional PQC signature over the chain tip with a NIST FIPS-finalized algorithm (default ML-DSA-65). Binds the case state to a specific custodian.
Chain of custody
ISO/IEC 27037 + NIST SP 800-86 sidecar JSON. Records who/what/when/where/why/how for every action.
Handling markings
Classification (UNCLASSIFIED / CUI / etc.) and TLP markings stored in case metadata, propagated to exports, and respected by sharing filters.
ISO/IEC 27037:2012
The ISO standard for the identification, collection, acquisition, and preservation of digital evidence. §6 mandates that the following be recorded for evidence to be admissible:
- Who acquired/handled it — named individual, role, contact
- What was acquired — device identifier, source path, size, hash
- When — UTC timestamp, time zone, time source
- Where — physical and logical location
- Why — legal authority, investigative scope
- How — tools, versions, methodology
NIST SP 800-86 adds the collection / examination / analysis / reporting lifecycle with documented integrity verification at each stage.
The chain-of-custody record
Source: digger/coc/record.py.
Created automatically when a case is opened. Lives at
<case-dir>/chain_of_custody.json. Sample:
{
"case_id": "d3a3be40-841c-43c7-9549-e31e7f464626",
"custodian_name": "analyst",
"custodian_role": "Analyst",
"custodian_contact": "",
"legal_authority": "host owner consent",
"investigative_scope": "endpoint forensic triage",
"target_host": "lockedin.local",
"target_os": "macOS-15.1-arm64-arm-64bit-Mach-O",
"time_zone": "PDT",
"time_source": "local OS clock",
"classification": "UNCLASSIFIED",
"handling_caveats": [],
"tlp": "TLP:AMBER",
"iso_27037_compliance": true,
"nist_800_86_compliance": true,
"events": [
{
"event_type": "case_opened",
"timestamp_utc": 1747780123.4,
"iso_8601": "2026-05-20T22:08:43Z",
"actor_name": "analyst",
"actor_user": "analyst",
"actor_host": "lockedin.local",
"location": "Darwin 25.1.0",
"methodology": "automated digger workflow",
"notes": "case directory initialized",
"tool": "digger",
"tool_version": "0.1.0"
},
{ "event_type": "collection_started", ... },
{ "event_type": "collection_finished", ... }
]
}
Recorded events
Lifecycle hooks append events automatically:
| Event | Triggered by |
|---|---|
case_opened | First write to the case directory |
collection_started / collection_finished | Each digger collect run |
scan_started / scan_finished | Each digger scan run |
triage_started / triage_finished | Each digger triage run |
report_generated | Each report rendering |
case_signed / case_verified | PQC sign / verify |
case_exported / case_imported | STIX/MISP/TAXII export |
case_encrypted / case_decrypted | Hybrid PQC encryption |
evidence_transferred | Manual: when the case directory changes hands |
case_closed | End of investigation |
manual_note | Free-form analyst annotation |
You can append manual events programmatically:
from digger.coc import open_custody
from digger.coc.record import append_event
coc = open_custody("./case-1", case_id="...")
append_event("./case-1", coc, "manual_note", "Reviewed by D. Smith, no concerns.")
Tamper detection
Three independent checks layer on top of each other:
- Chain verification —
digger verifywalks the whole DB. Mismatch cascades from the tampered row forward. - PQC signature —
digger pqc verifyover the chain tip. A modified chain tip won't verify against an earlier signature. - CoC event log — every digger operation appends an event. Gaps and timing mismatches in the event log are themselves evidence of tampering.
Classification + handling caveats
Set per case at collection time:
digger --classification CUI \
--tlp TLP:AMBER+STRICT \
investigate --case-dir ./case-1
Stored in the CoC record and in case metadata. Picked up by every exporter (STIX, MISP, ATT&CK Navigator) so externally-shared bundles carry the correct marking. Findings can override per-finding TLP through the triage step.
Compliance mapping
The bundled iso_27037 framework catalog evaluates digger's
own evidence handling against the standard. Run:
digger compliance assess --case-dir ./case-1 --frameworks iso_27037
It evaluates the controls that are mechanically checkable (presence of
artifacts, count, time records) and flags the rest as manual
for analyst sign-off (acquisition methodology, justifiability, repeatability).
Operational guidance
- Sign the chain tip immediately after collection, before any
analyst inspection, with a stored secret key not on the same host:
digger pqc sign --case-dir … --algorithm ML-DSA-65 --key /secure/keys/case-N.sk - Keep the case directory read-only between operations:
chmod -R a-w ./case-1and re-enable for the next run. - For transfers, archive the case directory plus
case_signature.jsonpluschain_of_custody.jsontogether. Re-rundigger verifyanddigger pqc verifyat the receiving end. - For long-term archival, hybrid-encrypt the archive:
digger pqc encrypt(forthcoming CLI wrapper) or calldigger.crypto.hybrid_encryptwith the recipient's ML-KEM-768 public key.