Chain of Custody for Digital Evidence in a Small Organisation
A practical guide for developers who need to preserve logs, disks, files, and exports so they remain defensible during an incident or HR/legal process. You’ll get a concrete workflow, commands, metadata to record, and the trade-offs that matter in a small organisation without a dedicated forensics team.
TL;DR — Chain of custody is not a formality; it is the record that proves a file, disk image, or log export is the same evidence you collected, stored, and analysed later. In a small organisation, the single highest-value move is to standardise one evidence workflow: collect read-only where possible, hash immediately with SHA-256, record every handoff in an append-only log, and store originals in immutable or tightly access-controlled storage. Reading time: ~7 min
What it is and where it sits
In incident response, chain of custody sits between evidence collection and evidence use. It is the operational record that answers four questions for every artefact:
- what exactly was collected
- when and from where it was collected
- who handled it at each step
- how you can prove it was not altered
For a small organisation, this usually applies to:
- disk or volume images
- exported cloud logs
- database dumps
- suspicious files from endpoints or email
- screenshots, chat exports, and ticket attachments
- firewall, VPN, IdP, and application logs
Architecturally, it lives alongside your IR process, not inside your app runtime. Your systems produce data; responders collect it; a custody log and evidence store track and preserve it; analysis happens on copies.
What it replaces: ad hoc Slack messages like “I copied the logs to my laptop” and random ZIP files named final2-really-final.zip. Those are fine until someone asks whether the file changed, whether timestamps were preserved, or who had access between collection and review.
View as text
[Prod Host] ---- 1. collect logs/image ----> [Collector Workstation] [Cloud APIs] ---- 1. export audit data ----> [Collector Workstation] [Collector Workstation] ---- 2. hash + manifest ----> [Custody Log] [Collector Workstation] ---- 3. upload original ----> [Evidence Store] [Evidence Store] ---- 4. verified copy ----> [Analysis Workspace] [Responder] ---- 5. handoff / access record ----> [Custody Log]
Typical request/data flow during an incident:
- A responder acquires evidence from a host, SaaS API, mailbox, or database.
- The responder computes hashes and writes a manifest plus custody entry.
- The original artefact goes to an evidence store with restricted write access or object lock/immutability if available.
- Analysts work from a copy, not the original.
- Every transfer, access grant, or derived artefact gets another custody entry.
For small teams, the minimum viable architecture is:
- one evidence naming convention
- one append-only custody log, ideally version-controlled or write-once in storage
- one restricted evidence bucket/share
- one analysis workspace isolated from originals
How it actually works
Walk one realistic example: an engineer suspects data exfiltration from a Linux VM running a customer-facing API. You need to preserve /var/log, relevant app logs, and a disk image of the attached volume.
Step 1: Create an evidence ID and start the custody record
Use a predictable ID like IR-2026-04-17-001. Put all notes under that ID.
Create a manifest directory locally:
mkdir -p evidence/IR-2026-04-17-001
cd evidence/IR-2026-04-17-001
printf '%s\n' '{"case_id":"IR-2026-04-17-001","collector":"alex","started_at_utc":"2026-04-17T09:14:22Z","source":"api-prod-03"}' > case.json
Then append the first custody event:
{"ts":"2026-04-17T09:14:22Z","case_id":"IR-2026-04-17-001","event":"collection_started","actor":"alex","source":"api-prod-03","notes":"Suspected exfiltration; preserving logs and attached volume before remediation"}
Store custody entries as newline-delimited JSON (.jsonl). That format is easy to append, diff, parse, and ship.
Step 2: Collect evidence with minimal alteration
For logs, use archive tools that preserve metadata.
⚠️ If you shut down a host, snapshot a volume, or run imaging tools against the wrong device, you can cause downtime or destroy evidence. Before running
dd,lsblkandmountthe system and have a second person confirm the device path.
On the host, archive logs:
sudo tar --xattrs --acls --selinux -cpf /tmp/IR-2026-04-17-001-logs.tar /var/log /srv/app/log
sudo sha256sum /tmp/IR-2026-04-17-001-logs.tar
Typical output:
8d4f6f5b5d8d4a2e0a0d2b9b8c7e1f0d5b3c2a1e9f8d7c6b5a4e3d2c1b0a998 /tmp/IR-2026-04-17-001-logs.tar
For a block device image, prefer a cloud snapshot if your platform preserves metadata and gives you a stable object to export later. If you must image locally, use dd with explicit block size and sync flags:
sudo lsblk -o NAME,SIZE,FSTYPE,MOUNTPOINT,MODEL
sudo dd if=/dev/nvme1n1 of=/mnt/forensics/IR-2026-04-17-001-nvme1n1.img bs=4M iflag=fullblock,noatime conv=sync,noerror status=progress
sudo sha256sum /mnt/forensics/IR-2026-04-17-001-nvme1n1.img
conv=noerror,sync keeps offsets aligned if read errors occur; note that this can pad unreadable sectors with zeros, which is acceptable only if you record it. status=progress gives a running byte count.
If dd hits trouble, output often looks like:
dd: error reading '/dev/nvme1n1': Input/output error
1048576+0 records in
1048576+0 records out
4398046511104 bytes (4.4 TB, 4.0 TiB) copied, 18234 s, 241 MB/s
Record that exact message in the custody notes.
Step 3: Hash immediately and write a manifest
Create a manifest containing filenames, sizes, hashes, source system, and collection commands.
stat --printf '%n,%s,%y\n' /tmp/IR-2026-04-17-001-logs.tar > manifest.csv
stat --printf '%n,%s,%y\n' /mnt/forensics/IR-2026-04-17-001-nvme1n1.img >> manifest.csv
sha256sum /tmp/IR-2026-04-17-001-logs.tar /mnt/forensics/IR-2026-04-17-001-nvme1n1.img > sha256sums.txt
Append custody events:
{"ts":"2026-04-17T09:28:01Z","case_id":"IR-2026-04-17-001","event":"artifact_created","actor":"alex","artifact":"IR-2026-04-17-001-logs.tar","sha256":"8d4f6f5b5d8d4a2e0a0d2b9b8c7e1f0d5b3c2a1e9f8d7c6b5a4e3d2c1b0a998","source":"api-prod-03:/var/log,/srv/app/log"}
{"ts":"2026-04-17T14:36:19Z","case_id":"IR-2026-04-17-001","event":"artifact_created","actor":"alex","artifact":"IR-2026-04-17-001-nvme1n1.img","sha256":"3f1a...","source":"/dev/nvme1n1","notes":"dd reported one input/output error; see case notes"}
Step 4: Transfer originals to evidence storage and verify after upload
Use a dedicated bucket or share with restricted write permissions. If your object storage supports retention lock or immutability, use it for originals. If not, use the strongest access control you have and keep a second copy offline or in a separate account/project.
Example with rclone to an S3-compatible bucket:
rclone copy /tmp/IR-2026-04-17-001-logs.tar evidence-remote:org-evidence/IR-2026-04-17-001/ --progress --checksum
rclone copy /mnt/forensics/IR-2026-04-17-001-nvme1n1.img evidence-remote:org-evidence/IR-2026-04-17-001/ --progress --checksum
rclone copy sha256sums.txt manifest.csv case.json custody.jsonl evidence-remote:org-evidence/IR-2026-04-17-001/
Then verify by re-downloading hashes or using remote checksum support:
rclone sha256sum evidence-remote:org-evidence/IR-2026-04-17-001
If the remote does not support SHA-256 natively, rclone may tell you:
2026/04/17 14:48:02 NOTICE: S3 bucket org-evidence: hash unsupported
In that case, download the object and hash it locally, or store your own checksum manifest as part of the evidence package.
Step 5: Analyse only copies
Create a working copy and verify before analysis:
cp /mnt/evidence/IR-2026-04-17-001/IR-2026-04-17-001-logs.tar /mnt/analysis/
cd /mnt/analysis
sha256sum -c /mnt/evidence/IR-2026-04-17-001/sha256sums.txt --ignore-missing
Expected output shape:
IR-2026-04-17-001-logs.tar: OK
If you see this, stop and investigate before proceeding:
IR-2026-04-17-001-logs.tar: FAILED
sha256sum: WARNING: 1 computed checksum did NOT match
Finally, append a handoff entry:
{"ts":"2026-04-17T15:02:11Z","case_id":"IR-2026-04-17-001","event":"analysis_copy_created","actor":"alex","artifact":"IR-2026-04-17-001-logs.tar","recipient":"priya","verification":"sha256 OK before handoff"}
That is the core mechanism: collect, hash, record, store original, analyse copy, log every handoff.
When to use it (and when not to)
| Scenario | Recommendation |
|---|---|
| Security incident with possible legal, HR, customer, or insurer involvement | Use full chain of custody from the first collection step |
| Suspected insider activity or employee misconduct | Use full chain of custody; expect scrutiny of access and timing |
| Malware on a production endpoint/server | Use it for originals and critical exports before cleanup |
| Routine debugging of a 500 error or performance regression | Usually overkill; normal logging and ticket history are enough |
| Temporary sandbox or disposable dev environment with no sensitive data | Probably not needed |
| You only need metrics trends, not artefact preservation | Use observability retention, not forensic custody |
You probably do not need formal chain of custody if all of these are true:
- the issue is operational, not disciplinary or legal
- no one will rely on the artefact as evidence later
- you can recreate the data from normal logs or test traffic
- there is no realistic dispute about who changed what
You do need it early if there is any chance of external reporting, regulator contact, customer claims, employee action, or law enforcement. Retrofitting custody after three people copied files around Slack is weak and often impossible.
Trade-offs
| Benefit | What it costs |
|---|---|
| You can prove integrity with hashes and manifests | Extra collection time, storage, and process discipline |
| Handoffs are traceable | People must log every transfer and access, which they will forget unless templated |
| Originals remain untouched | You need separate storage and an analysis workflow |
| Better legal/HR defensibility | More coordination with management, HR, or counsel |
| Repeatable incident handling | Up-front work to define naming, retention, and permissions |
| Reduced accidental tampering | Slower emergency response if the team is not practiced |
The main small-org trade-off is speed versus defensibility. During an outage, people want to “just grab the logs.” That is fine for debugging, but if the incident may turn contentious, spending an extra 10 minutes on hashes and a custody log is usually worth more than another hour of speculative analysis on untracked files.
In practice
Example 1: Minimal custody log template
{"ts":"2026-04-17T09:14:22Z","case_id":"IR-2026-04-17-001","event":"collection_started","actor":"alex","source":"api-prod-03","notes":"Suspected exfiltration"}
{"ts":"2026-04-17T09:28:01Z","case_id":"IR-2026-04-17-001","event":"artifact_created","actor":"alex","artifact":"IR-2026-04-17-001-logs.tar","sha256":"8d4f6f5b5d8d4a2e0a0d2b9b8c7e1f0d5b3c2a1e9f8d7c6b5a4e3d2c1b0a998","size_bytes":73400320}
{"ts":"2026-04-17T14:48:55Z","case_id":"IR-2026-04-17-001","event":"stored_original","actor":"alex","location":"s3://org-evidence/IR-2026-04-17-001/","notes":"Upload completed; checksum manifest stored alongside artifacts"}
{"ts":"2026-04-17T15:02:11Z","case_id":"IR-2026-04-17-001","event":"analysis_copy_created","actor":"alex","recipient":"priya","artifact":"IR-2026-04-17-001-logs.tar","verification":"sha256 OK"}
This is enough to start today: append-only JSONL, one event per line. The gotcha is consistency: if half the team writes free-form notes and half records hashes and recipients, the log becomes hard to trust and automate.
Example 2: Bash collection script for a Linux host
#!/usr/bin/env bash
set -euo pipefail
CASE_ID="$1"
OUTDIR="/mnt/forensics/${CASE_ID}"
HOST="$(hostname -f)"
TS="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
mkdir -p "$OUTDIR"
LOG_TAR="${OUTDIR}/${CASE_ID}-${HOST}-logs.tar"
CUSTODY="${OUTDIR}/custody.jsonl"
MANIFEST="${OUTDIR}/sha256sums.txt"
echo "{\"ts\":\"${TS}\",\"case_id\":\"${CASE_ID}\",\"event\":\"collection_started\",\"actor\":\"${USER}\",\"source\":\"${HOST}\"}" >> "$CUSTODY"
sudo tar --xattrs --acls --selinux -cpf "$LOG_TAR" /var/log /srv/app/log
sha256sum "$LOG_TAR" | tee -a "$MANIFEST"
HASH="$(awk '{print $1}' "$MANIFEST" | tail -n1)"
SIZE="$(stat -c '%s' "$LOG_TAR")"
TS2="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
echo "{\"ts\":\"${TS2}\",\"case_id\":\"${CASE_ID}\",\"event\":\"artifact_created\",\"actor\":\"${USER}\",\"artifact\":\"$(basename "$LOG_TAR")\",\"sha256\":\"${HASH}\",\"size_bytes\":${SIZE}}" >> "$CUSTODY"
This script collects logs, hashes the archive, and writes two custody events. The gotcha is privilege and side effects: tar reads files in place, but if your logs rotate during collection you can get inconsistent captures; note the collection window and, for critical systems, snapshot first if your platform supports it.
Further reading
- NIST SP 800-86, Guide to Integrating Forensic Techniques into Incident Response
- RFC 3227, Guidelines for Evidence Collection and Archiving
- The SANS Digital Forensics and Incident Response poster and cheat sheets
- The "Logging Cookbook" section of the Python documentation
- The "Use
sha256sum" and coreutils manual pages
This article was written by an AI system and published pending human review. Verify anything you intend to act on.
Have a project in mind?
Get an instant AI price estimate for it, or talk directly to our team.
One email a month on what we learn building with AI