Skip to content

Proving a finding six months later

Generating an alert takes an afternoon. Answering for it takes an architecture decision: what you stored, what you kept, and whether a second person can arrive at your number from your inputs.

(updated 2026-10-18)defensible security findingsdetection engineering evidence retentiontamper-evident audit log

The review is the test, not the alert

Six months later, three audiences ask about the same week: a post-incident reviewer asking whether you should have seen it, an auditor asking whether a control operated, and a customer asking what you knew. All three ask the same four questions. What did the detector see? Why did it score the way it did? Could this have been benign? Could the record have been altered since?

A title and a colour answer none of them. The work that makes a finding defensible happens in the seconds around detection, when the inputs are still in memory, and it is mostly a decision about what to persist.

Four things that have to be true at detection time

The inputs are stored, not just the verdict

Storing the number 9.5 explains nothing. Storing the number alongside a CRITICAL base, a signal count of two, and the fact that no exposure, crown-jewel, regulated-data, KEV or EPSS multiplier applied explains the whole result to anyone who reads it. The difference between those two records is one JSON field and every future argument you will have.

The rule is identified, and the code is versioned

A rule id is a stable identifier; a rule body is not. Thresholds get tuned, windows move, a matcher gets sharpened after a false negative. If you re-run today's engine against last quarter's events and get a different answer, you have not reproduced the finding — you have derived a new one and quietly relabelled it. Record the rule id and be able to say which version of the rule ran.

The artefacts are ones that do not move

  • A commit SHA, not a branch name. Branches are reused and force-pushed; the SHA is the artefact.
  • An access key id, not an IP address. Addresses are reassigned; the key id outlives the key and matches the audit trail.
  • A workflow path, a package name with an exact version, a certificate fingerprint with its issuer, a region, a session identifier.
  • Timestamps in UTC, from the source system, not from the console that rendered them.

The window is on the finding

A correlated finding is a claim about order and proximity, so the window is part of the claim. Tampering then a leak within seven days. A package release then model abuse within seven days. Credential validation, then enumeration, then invocation with no more than twelve hours between stages. If the incident does not carry its window, a reviewer cannot tell whether the pair was a chain or a coincidence, and neither can you.

The arithmetic is the part that survives

Prioritisation is where security teams lose arguments, because it is where judgement looks like taste. A deterministic score converts that argument into a table that a reviewer can check line by line.

InputEffectWhere it lives
Base severityCRITICAL 9.0, HIGH 7.0, MEDIUM 5.0, LOW 3.0, INFO 1.0SEVERITY_BASE
Each additional contributing signal+0.5, capped at +2.0computeCompositeScore
Publicly exposed×1.3computeCompositeScore
Crown-jewel reach×1.6computeCompositeScore
Regulated data in scope×1.4 for PII, PHI or PCI; ×1.1 for other classescomputeCompositeScore
Listed in CISA KEV×1.5computeCompositeScore
EPSS above 0.5×(1 + 0.4 × EPSS)computeCompositeScore
Finalcapped at 10.0, two decimals; bands ≥9 CRITICAL, ≥7 HIGH, ≥5 MEDIUM, ≥3 LOWscoreToSeverity
The scoring path, in full

Two worked examples make the value of this obvious. A CI tampering chain scored with a CRITICAL base and two contributing signals is 9.0 + 0.5 = 9.5, which is CRITICAL. The same chain against a crown-jewel asset is 9.5 × 1.6 = 15.2 before the cap, which is 10.0 — and the reviewer can see that the crown-jewel flag is what saturated it, rather than a number appearing from nowhere.

That is the property to look for in any prioritisation you inherit: given the stored inputs, a second person computes the same output. If they cannot, you are defending an opinion, and opinions do not survive a review.

What survives, what expires

Signals on the bus are transient by design. A detection emitted onto the bus carries a time-to-live — the LLMjacking engine uses one hour for its anomaly signals and two hours for its kill-chain signal — after which the entry is gone. That is correct behaviour for a stream and a trap for an audit.

The durable artefact is the risk row, and it has to be self-contained: the rule id, the contributing signal ids and types, the structured attack chain with the duration between steps, the affected entities, the score. A pointer to a stream entry that expired in October is not evidence in April, it is a reference to something that no longer exists.

Retention is a decision you make on purpose. Configure the audit retention period against your longest obligation rather than your incident response process, because the customer questionnaire in month eleven will ask about a window that ended long before anyone worried about it.

Tamper evidence, stated accurately

The audit log is hash chained per tenant. Each entry's entryHash is sha256(prevHash | serialised-payload) over a deterministically serialised record, with sequence numbers issued inside a serializable transaction so two writers cannot fork the chain. Change a row, delete a row, or insert one with a forged timestamp, and the next row's hash no longer matches. A walk of the chain finds it.

Three limits belong in the same paragraph, because overstating this is how a security team loses credibility in a review.

  • It is evidence within the log. It proves the record was not silently edited without everything after it being recomputed. It does not, on its own, prove the contents of the log to a third party, because that third party is trusting the same database you are.
  • An actor who can rewrite the chain from a point forward can recompute it. Integrity against that case requires an external anchor: a head hash shipped somewhere else, a signed copy, or a write-once target that is not the primary store.
  • A valid hash proves order and integrity, not truth. If the collector never saw the event, the log is a perfectly chained record of nothing.

The sentence to give an assessor is narrow and true: tampering is evident within the log, and independent verification against an external copy is something we run ourselves. That sentence holds up. A claim that the log proves itself does not.

Why the scanner said high does not survive

A severity label from a tool you did not feed is an assertion, not an argument. It collapses under the second question — what input produced it — because no input was recorded, and the threshold that made it high belongs to somebody else's product roadmap.

There is a second, quieter failure in the same shape. Re-running today's rules against today's storage and getting a similar-looking finding is not reproduction either. The raw events may have aged out, the rule may have been retuned, and the entity may have been renamed. Reproduction means the same rule version, the same inputs, the same window, and a documented reason for every difference you cannot avoid.

Checklist for the next review

The questionThe artefact
Why is this HIGH and not MEDIUM?the score inputs and rule id stored on the risk row, plus the band table
What specifically fired, and in what order?contributing signal ids and types, with the measured duration between the first and last step
Could this have been benign?the same inputs re-scored; the reviewer can see which single input flips the band
Who could have changed the record?the per-tenant hash chain with sequence numbers, and wherever you keep the independent copy
Is the same thing true today?the rule id and stored inputs; re-run and compare, rather than assuming
The question, and the artefact that answers it

Most teams can answer the second row and nothing else. The first and fourth are the ones that decide whether the review goes well.

Questions

Does a hash-chained audit log prove nothing was tampered with?
It makes tampering evident within the log: any edited, removed or inserted row breaks the next row's hash. It does not by itself prove the log's contents to a third party, and it does not stop someone who can rewrite the whole chain from recomputing it forwards. For that you need a copy or a signed head held somewhere the primary store's administrator cannot reach.
Do I need to keep every raw signal to reproduce a score?
No. Keep the inputs the score consumed — base severity, contributing signal count, and which multipliers applied — plus the identifiers that let you go back to the raw event while it still exists. Storing the whole stream forever is a much larger project than storing the reasoning, and the reasoning is what a reviewer asks about.
What breaks if we tune the rule set?
Reproducing an old finding with a new rule set is not reproduction; it is a new derivation wearing the old label. Keep the rule id and the stored inputs, and when a threshold changes, note the change against the findings that used the previous value. A reviewer will accept a documented tuning decision and reject an unexplained difference.
How long should evidence be retained?
Longer than your longest obligation, and set deliberately rather than inherited. Map the retention period to the framework or contract that reaches furthest back, confirm the audit chain covers the same period, and test the retrieval path once so you discover the gap before an assessor does.
Is a deterministic score automatically a correct one?
No. Determinism means the same inputs produce the same output for everyone; it says nothing about whether the thresholds are right. The advantage is that a wrong threshold is visible, arguable, and fixable in one place — not hidden behind a model whose next version might silently disagree.

Run it in your own account

One Docker Compose stack, your datastores, read-only credentials into your clouds. The Community edition is free to run in production.