Evidence Packs
Evidence Packs are forensic-grade export bundles for legal, compliance, and security use cases.
What's in an Evidence Pack?
Every pack includes:
| Artifact | Purpose |
|---|---|
manifest.json | Pack metadata, version, timestamps |
chain_of_custody.json | SHA-256 hashes for ALL artifacts |
finding.json / scan_summary.json | Core detection data |
evidence/ | Detection evidence, behavior signals |
har/ | HTTP Archive (network traffic) |
not_captured.json | Explicit list of unavailable artifacts |
Chain of Custody
🚫
NON-NEGOTIABLE: Every artifact in a pack has a cryptographic hash.
The chain_of_custody.json file contains:
{
"pack_type": "finding",
"pack_id": "12345",
"bundle_hash": "a1b2c3d4...",
"artifacts": [
{ "path": "finding.json", "sha256": "...", "size_bytes": 1234 },
{ "path": "evidence/detection.json", "sha256": "...", "size_bytes": 567 }
],
"not_captured": [
"screenshots/initial.png - Screenshot capture not enabled"
],
"generated_at": "2024-01-15T10:30:00Z",
"generator_version": "1.0.0"
}Bundle Hash
The bundle_hash is deterministic:
- Same finding/scan = Same hash
- Timestamps excluded from hash computation
- You can verify integrity by re-computing
Pack Types
Finding Pack
Export evidence for a specific vendor finding.
Contents:
- Finding metadata (vendor, categories, scores)
- All scan occurrences of this finding
- Vendor-specific HAR excerpt
- Behavior signals
Use case: Vendor review, legal analysis, compliance documentation
Scan Pack
Export complete scan results.
Contents:
- Full scan metadata
- Complete HAR file
- All cookies captured
- All findings from this scan
- Reproduction parameters
Use case: Point-in-time audit, incident investigation
Generating Packs
From Console
- Navigate to a Finding or Scan
- Click Download Pack
- ZIP file downloads automatically
Via API
# Finding Pack
GET /api/findings/{id}/pack
# Scan Pack
GET /api/intel/scan/{id}/packVerifying Integrity
To verify a pack hasn't been tampered with:
import crypto from 'crypto'
import JSZip from 'jszip'
async function verifyPack(zipBuffer) {
const zip = await JSZip.loadAsync(zipBuffer)
const custody = JSON.parse(await zip.file('chain_of_custody.json').async('string'))
// Verify each artifact
for (const artifact of custody.artifacts) {
const content = await zip.file(artifact.path).async('arraybuffer')
const hash = crypto.createHash('sha256').update(Buffer.from(content)).digest('hex')
if (hash !== artifact.sha256) {
throw new Error(`Hash mismatch for ${artifact.path}`)
}
}
// Verify bundle hash
const sortedHashes = custody.artifacts
.sort((a, b) => a.path.localeCompare(b.path))
.map(a => a.sha256)
.join('')
const computedBundle = crypto.createHash('sha256').update(sortedHashes).digest('hex')
if (computedBundle !== custody.bundle_hash) {
throw new Error('Bundle hash mismatch')
}
return true
}Not Captured
Evidence packs explicitly list what ISN'T included:
{
"not_captured": [
"screenshots/initial.png - Screenshot capture not enabled in scanner",
"scripts/*.js - Script body extraction not implemented (CORS/integrity)",
"cookies/pre_consent.json - Pre/post consent split not implemented"
]
}This ensures:
- No claims of evidence that doesn't exist
- Clear documentation of limitations
- Honest forensic representation
Legal Use Cases
Vendor Termination
- Export finding pack as proof of violation
- Include in termination notice
- Hash provides tamper-evidence
Compliance Audit
- Export scan packs at regular intervals
- Store in compliance archive
- Hash chain proves timeline integrity
Litigation Support
- Bundle hash = unique identifier
- Chain of custody = forensic standard
- Timestamps prove when evidence was captured
Best Practices
- Export immediately when discovering issues
- Store packs externally (not just in BLACKOUT)
- Verify hashes when presenting evidence
- Document the not_captured list in any report
- Never modify pack contents after export
Next Steps
- Reports — Generate Weekly Brief PDFs
- Workspaces — Configure Legal/GRC workspace
- Reference: API — Programmatic pack generation