Digital Forensics / Advanced: models, validation, correlation, and reporting / Lesson 16
Correlate artifacts without multiplying certainty
Check identity scope, join cardinality, independent lineage, and alternative explanations before combining observations.
On this page
Lesson goals
- Use scoped keys and account for one-to-many relationships.
- Distinguish corroboration from repeated derivatives of one source.
Define the relationship being tested
Correlation should answer a specific question, such as whether a recorded import id links a source version to a later application write. Similar text or nearby time is only a clue. State the expected relationship, identifier scope, and possible alternatives before joining tables. An event id may be unique only within one source or session, while a path can change or be reused. Keep raw references and timing limitations. The fictional workshop artifacts are generated for learning and support no real identity, causality, or incident finding beyond their declared relationships.
Use the full declared identity scope
The first program shows why event id alone can join unrelated sources. It uses a source/id composite key and retains only the intended match. The source label is a trusted fixture input in this exercise, not a value that a real attacker should be allowed to assert as provenance. Actual correlation needs evidence for how source identifiers and event ids were generated and retained. A successful equality comparison establishes a data relationship under those assumptions; it does not automatically establish that the underlying event occurred exactly as described or that the source is complete.
left = [{"source":"app-a","event":"7","version":2}]
right = [{"source":"app-b","event":"7","note":"unrelated"}, {"source":"app-a","event":"7","note":"matching"}]
key = lambda row: (row["source"], row["event"])
joined = [(a["version"], b["note"]) for a in left for b in right if key(a) == key(b)]
assert joined == [(2, "matching")]
assert left[0]["event"] == right[0]["event"]
print("scoped key avoids the unrelated equal event id")
scoped key avoids the unrelated equal event id
The complete program compares three local records and uses no database engine. It checks the supplied key rule only. It discovers no real event relationship and does not authenticate any source label.
Check whether apparent sources are independent
Two exported reports may both derive from one log and therefore repeat the same missing records or parser error. The second program represents each derivativeโs declared roots and detects overlap. This is a lineage model, not a statistical independence test. Distinct roots can still share a clock, identity provider, or collection mechanism, so assess those dependencies too. Independence is a supported contextual property, not a count of tools. Preserve the original material-to-derivative graph so later reviewers can understand which observations genuinely add information and which merely restate another view.
roots = {"report-a":{"log-1"}, "report-b":{"log-1"}, "report-c":{"fixture-2"}}
def shares_declared_root(a, b):
return bool(roots[a] & roots[b])
assert shares_declared_root("report-a", "report-b") is True
assert shares_declared_root("report-a", "report-c") is False
print("shared declared lineage detected without a confidence score")
shared declared lineage detected without a confidence score
The complete program checks overlap between invented root sets. It does not prove genuine independence of report-c or calculate evidentiary confidence. All lineage facts are supplied by the fixture rather than discovered from actual evidence.
Explain every joined and unmatched row
A one-to-many join can multiply rows without representing additional events. Duplicate deliveries, repeated identifiers, and overlapping intervals can inflate counts. State expected cardinality and reconcile input rows, matches, duplicates, and unmatched records. A missing match may reflect retention, a different key scope, or a parser issue rather than absence of activity. Keep contradictory values visible instead of choosing one silently. Where a join depends on normalized text or timestamps, preserve the normalization rule and uncertainty. The report should explain why the joined observations support the proposed relationship rather than merely displaying a large merged table.
Combine observations without hiding assumptions
A defensible correlation statement identifies the objects, keys, timing basis, lineage, matching results, and remaining alternatives. Do not assign a precise probability merely because several rows agree. If sources disagree, trace the first unsupported conversion or relationship and preserve both views. The local examples establish scoped equality and declared lineage overlap only. They do not prove a real action, person, or cause. Keep the analytical argument reproducible and proportional to the information actually added by each artifact, including the limitations of missing or dependent observations.
Common errors and fixes
Equal ids from different sources are merged
- Why it happens
- Identifier uniqueness scope is omitted.
- How to fix it
- Use the documented composite identity and preserve source context.
- Prevent it next time
- Test deliberate same-id different-source fixtures.
Two derivative reports count as independent witnesses
- Why it happens
- Shared lineage is not examined.
- How to fix it
- Map roots and relevant common dependencies.
- Prevent it next time
- Explain what information each artifact adds.
Joined row count is called event count
- Why it happens
- One-to-many relationships inflate the result.
- How to fix it
- Reconcile cardinality and event identity before counting.
- Prevent it next time
- Retain duplicates and unmatched categories explicitly.
Check your understanding
1. Two tools produce matching timelines from log-1. Is independent corroboration established?
Show worked solution
No. Both outputs share a declared source and may repeat its errors or omissions. Compare parser assumptions and seek a genuinely separate observation where authorized. Report agreement between derivatives as such, without multiplying confidence merely because two tool names appear.
2. One application event matches three collector receipts. How should the count be described?
Show worked solution
Describe one supplied event related to three receipt records under the documented key, subject to source completeness and meaning. Determine whether receipts represent duplicate delivery, stages, or another relationship before reporting activity counts. Preserve all rows and the cardinality rationale.
Key takeaways
- Correlation needs scoped identity and explicit cardinality.
- Repeated derivatives do not automatically add independent evidence.
- Agreement is useful only with explained assumptions and alternatives.