incident-response · · 14 min read

From Alert to Case: Preserving the Evidence Chain

Turning an alert into a case should preserve the original record, provenance, timestamps, hashes, custody events, and exportable evidence without claiming more forensic assurance than the pipeline can prove.

From Alert to Case: Preserving the Evidence Chain

An alert changes character when it enters a case.

In the SIEM, it is one detection result among many. In a case-management system, analysts add an owner, severity, observables, tasks, notes, attachments, enrichment, and response decisions. The record becomes easier to work. It can also become harder to explain.

A copied field may lose its source. A parser may normalize a timestamp without preserving the original. An attachment may be downloaded and uploaded again under the same name. An observable may be deduplicated without showing which alerts supplied it. A responder may add threat-intelligence context that later looks like sensor evidence. By the time the case is exported, nobody can tell which facts arrived with the alert and which facts were added during investigation.

I want the case to improve the evidence chain, not replace it. That means preserving the exact bytes observed at the first controlled capture boundary, assigning evidence IDs, recording every transformation, separating observations from conclusions, hashing the right bytes, and exporting enough provenance for another qualified person to reconstruct what happened.

It also means being honest about limits. A SOAR audit log is useful. It is not automatically a forensic chain of custody. A SHA-256 value can detect later byte changes. It cannot prove who collected the bytes, whether the source was complete, whether the clock was right, or whether the collector was already compromised.

Start with three distinct records

I separate three things that are often collapsed into one object:

  1. The source record is what the producing system emitted or returned.
  2. The normalized record is a versioned interpretation used across the pipeline.
  3. The case record is the analyst's working state, including decisions and derived material.
Start with three distinct records

The source record is preserved without enrichment. If an API wraps the alert in transport metadata, I preserve the response body and record relevant request context separately. I do not insert case IDs into the original JSON and then call it original.

The normalized record can map product-specific fields into a common schema. It should carry references back to the source acquisition, the transformation code version, and the transformation time. The case record references both. It remains mutable because investigations require correction and collaboration.

This separation lets an analyst improve the case without rewriting history.

TheHive can create a case from an alert and transfer data including observables, tactics and techniques, attachments, comments, and custom fields. That behavior is useful, but the resulting case still needs a provenance contract. A transferred observable should retain the alert reference that supplied it. A copied attachment should retain the hash of the bytes acquired from the source. A comment copied into a case should not become indistinguishable from a later analyst note.

Define the acquisition boundary

Evidence handling begins before case creation. The first controlled boundary may be a SIEM export API, a message queue consumer, a webhook receiver, or a file collector. Document it.

Define the acquisition boundary

For each acquisition I record:

  • a globally unique evidence ID
  • source system and tenant
  • source-native alert or event ID
  • collection method and endpoint class
  • query or cursor reference, with secrets removed
  • collector workload identity
  • initiator identity when a person requested collection
  • start and completion times in UTC
  • source-reported event and ingestion times
  • exact byte length
  • media type and content encoding
  • cryptographic hash of the acquired bytes
  • storage object version or immutable object reference
  • parser and schema versions
  • completeness and error indicators

A representative envelope looks like this:

{
  "schema_version": "evidence.acquisition.v1",
  "evidence_id": "ev-01JEXAMPLE",
  "source": {
    "system": "siem-a",
    "tenant_id": "soc-blue",
    "record_type": "alert",
    "native_id": "alert-77520"
  },
  "acquisition": {
    "method": "api-read",
    "initiator_id": "analyst-17",
    "executor_id": "case-ingest-prod",
    "started_at": "2026-09-19T10:14:02.184Z",
    "completed_at": "2026-09-19T10:14:02.421Z",
    "result": "complete",
    "bytes": 18422,
    "media_type": "application/json"
  },
  "integrity": {
    "algorithm": "sha256",
    "value": "EXAMPLE",
    "covers": "source-response-body"
  },
  "storage": {
    "repository": "incident-evidence",
    "object_id": "cases/2026/0042/ev-01JEXAMPLE/source.bin",
    "object_version": "version-7",
    "write_once_until": "2027-09-19T00:00:00Z"
  },
  "interpretation": {
    "parser": "siem-alert-normalizer",
    "parser_version": "3.4.1",
    "schema": "normalized.alert.v2"
  }
}

The hash must say what it covers. Hashing a pretty-printed JSON export and later comparing it with the source response will fail even when the data values are equivalent. Hashing only an attachment while implying that the hash covers its metadata overstates the check.

For JSON that needs a stable semantic digest, use a documented canonical representation. RFC 8785 specifies a JSON canonicalization scheme for cryptographic operations. I still retain the original bytes. A canonical digest supports comparisons across equivalent serializations; it does not replace the acquisition hash.

Preserve the clocks, including disagreement

Security records commonly carry several times:

  • the device says the activity occurred
  • the agent says it observed the activity
  • the manager received the event
  • the SIEM indexed it
  • the rule produced an alert
  • the pipeline acquired the alert
  • the case platform created the case
  • an analyst or responder performed an action
Preserve the clocks, including disagreement

Those are different facts. I do not collapse them into a field called timestamp.

Use UTC with an explicit offset for interchange. RFC 3339 provides a precise timestamp profile for Internet protocols. Preserve the source's original time string as well as the parsed value, timezone assumption, and parser version. If a source provides local time without an offset, mark the interpretation and its uncertainty. Do not append Z and pretend the source supplied UTC.

Clock quality belongs in provenance. Record measured offset, measurement time and source, and parser assumptions when available. Missing clock evidence should remain a stated limit. A timeline can still be operationally useful without courtroom-grade time assurance, but the case should not hide the distinction.

Timesketch organizes work into sketches, commonly one per case, with multiple timelines and views. That makes it useful for collaborative temporal analysis. Its own model does not guarantee that imported timestamps were correct. Import provenance, source clock notes, and transformation history still need to travel with the events or accompanying case documentation.

Assign evidence IDs before observables are deduplicated

An IP address, domain, file hash, process name, and user account are observables. They are not evidence objects by themselves. The evidence is the source record or acquired artifact that reported the observable under particular conditions.

Assign evidence IDs before observables are deduplicated

If five alerts mention one address, a case platform may reasonably show one observable. The provenance must retain five relationships:

observable:
  observable_id: obs-01JEXAMPLE
  type: ipv4
  value: 192.0.2.44
  normalized_value: 192.0.2.44
  relationships:
    - evidence_id: ev-01JEXAMPLE
      json_pointer: /data/network/source_ip
      role: source_address
    - evidence_id: ev-01JEXAMPLE2
      json_pointer: /event/client/ip
      role: client_address
  analyst_assessment:
    disposition: suspicious
    assessed_by: analyst-17
    assessed_at: 2026-09-19T10:41:20Z
    rationale_ref: note-22

The value and the assessment live in different namespaces. 192.0.2.44 may be an observed source address. Calling it a command-and-control server is an analyst conclusion that needs its own author, time, rationale, and supporting evidence.

Cortex can run analyzers and responders on observables and other TheHive objects. Analyzer output should enter the case as derived evidence with the analyzer name, version, configuration profile, execution ID, start and completion times, input observable reference, output hash, and any external data-source timestamps. It must not be presented as though the original sensor emitted it.

Track initiator and executor separately

Automation tends to turn attribution into one service account. I record at least four actors when they exist. The initiator is the analyst, alert rule, or scheduled job that started the operation. The approver authorized a protected action. The executor is the workload identity that made the API call. The custodian is the person or repository that controlled the evidence after acquisition.

Track initiator and executor separately

The source rule is also part of attribution. Record its ID and version. If Shuffle receives a Wazuh alert and creates a TheHive record, the chain should name the Wazuh rule revision, the Shuffle workflow revision and execution ID, the TheHive API identity, and the resulting alert or case ID. Human approval, if required, remains separate from the workload that executes the write.

TheHive and Cortex activity can support this trail, while Shuffle or StackStorm can preserve orchestration execution details. I also send normalized audit events to a separate log path. A platform should not be the only keeper of evidence about its own privileged actions.

Do not copy secrets into the audit record. Credential IDs, key versions, certificate fingerprints, and workload identities are usually enough. Authorization headers, session cookies, webhook secrets, and API keys are not evidence fields.

Make case creation idempotent

Alert delivery is at least once in many practical pipelines. A webhook can be retried. A consumer can restart after creating the case but before committing its cursor. An analyst can press the action twice.

Make case creation idempotent

Case creation needs a stable idempotency key:

sha256(JCS({"schema":"case-request.v1","source_system":source_system,
"tenant_id":tenant_id,"source_alert_id":source_native_alert_id,
"routing_policy_version":case_routing_policy_version}))

Persist the key with the destination object ID. Before creating a case, query the local mapping and the case platform for an existing external reference. After an ambiguous timeout, read before retrying.

HTTP does not make POST idempotent by default. RFC 9110 defines which methods are idempotent at the protocol-semantic level. Application-level deduplication is still required when a case API uses POST to create records.

A duplicate should not disappear without evidence. Record a delivery event with outcome DEDUPLICATED, the existing case ID, the source alert ID, and the decision time. If a later alert is merged into an existing case, preserve that relationship rather than pretending the alert created the case.

Approval and concurrency apply to evidence too

Creating a blank case may be low impact. Exporting personal data, collecting a memory image, running a remote response action, changing retention, sealing an incident package, or deleting duplicate material is not.

Approval and concurrency apply to evidence too

I classify actions by authority:

case_action_policy:
  create_case_from_alert:
    approval: automatic
    preconditions: [source_preserved, idempotency_key_unique]
  collect_endpoint_artifact:
    approval: analyst
    preconditions: [target_bound, scope_rendered, storage_ready]
  collect_memory:
    approval: incident_commander
    concurrency_scope: endpoint
  delete_evidence:
    approval: records-owner
    automatic: false

Concurrency matters because two collectors can change the source, overload an endpoint, or produce confusing custody records. Acquire a lock for the endpoint and collection class when needed. Record the lock owner and lease. The approval should bind to the exact target, artifact set, collection tool version, destination, and expiry.

A useful dry run renders which files, registry keys, event-log channels, process data, memory ranges, or query intervals the collector will request. It also shows expected size, timeout, destination, retention class, and potential source impact. Dry run is an evidence object because it documents the approved scope.

Velociraptor collects endpoint artifacts through client collections and records collection context in its platform. For case use, export or reference the collection ID, artifact definitions and versions, parameters, target client ID, timestamps, logs, and result files. Preserve the artifact definition used at execution time. A later update to the same artifact name must not rewrite what the prior collection meant.

Every transformation needs a receipt

Normalization, decompression, decoding, filtering, redaction, format conversion, timeline extraction, and enrichment are transformations. Each one should produce a receipt.

Every transformation needs a receipt
{
  "schema_version": "evidence.transformation.v1",
  "transformation_id": "tx-01JEXAMPLE",
  "input_evidence_ids": ["ev-01JEXAMPLE"],
  "operation": "normalize-siem-alert",
  "tool": "case-ingest",
  "tool_version": "3.4.1",
  "configuration_digest": "sha256:EXAMPLE",
  "executor_id": "case-ingest-prod",
  "started_at": "2026-09-19T10:14:02.430Z",
  "completed_at": "2026-09-19T10:14:02.447Z",
  "result": "complete",
  "output_evidence_ids": ["ev-01JEXAMPLE-N"],
  "input_hashes": ["sha256:EXAMPLE"],
  "output_hashes": ["sha256:EXAMPLE"],
  "losses": ["transport-header-order-not-retained"]
}

The losses field forces honesty. A CSV export may lose nested structure, numeric types, original field order, null distinctions, and timezone detail. A screenshot preserves appearance but usually loses searchable structure and off-screen fields. A timeline extractor may select only events matching a rule. Those outputs can be useful, but they are not lossless copies.

NIST SP 800-86 discusses integrating forensic techniques into incident response and addresses proper evidence handling, chain of custody, integrity, and storage. A transformation receipt is one way to turn those concerns into a repeatable system record.

Hash bytes, then protect the hash and manifest

A hash stored beside a mutable file in the same account is weak evidence against an administrator or compromised service. An attacker able to replace both can calculate a new pair.

Hash bytes, then protect the hash and manifest

I hash source bytes at acquisition and store them under an immutable or versioned object reference. I then canonicalize and sign a manifest with a separate identity, send its digest to an independent audit destination, and verify hashes during reads, transfers, exports, and restore tests.

Signing proves that the holder of a signing key approved a particular digest. It does not prove the source was truthful. Key custody, certificate validity, revocation, algorithm choice, and signing time remain part of the assurance statement.

A trusted timestamp can strengthen proof that a digest existed by a certain time when the timestamp authority and validation material are available. RFC 3161 defines a time-stamp protocol for binding a time value to a hash imprint. Many internal investigations do not need a formal timestamp authority. They still need synchronized clocks, append-only audit delivery, and clear limits on what their timestamps prove.

Read back every boundary

A successful write call proves that a server returned success. I verify the destination object and its relationships.

Read back every boundary

After alert ingestion, I read the alert back by destination ID. I compare its source ID and idempotency key, confirm attachment hashes and observable relationships, and verify that the workflow did not create an unexpected case.

After case creation or merge, I verify source-alert references, classification, ownership, policy fields, audit attribution, and organization boundaries. Attachment checks download and hash either every object or a documented sample.

After export, I unpack the package in isolation, validate the manifest signature, recalculate file hashes, reconcile expected and actual counts, and open representative artifacts with independent tools. Source, normalized, derived, and analyst-authored records must remain distinguishable.

OpenSearch snapshots preserve indexes and cluster state for backup and recovery. They are valuable for restoration, but a cluster snapshot is not automatically a case export. It may contain unrelated records, depend on matching software, and omit case-platform objects stored elsewhere. A defensible case package needs a scoped manifest and verification procedure.

Use self-hosted security tools according to their evidence role

No one product owns the complete chain.

Use self-hosted security tools according to their evidence role
Tool Useful role Boundary I document
Wazuh Endpoint and manager telemetry, alert generation, active-response records Whether raw archives are enabled, which manager produced the alert, and whether the record reached the indexer
Graylog Central collection, routing, search, and analyst export Search interval, query, stream, fields, timezone, export format, and known export losses
OpenSearch Searchable event storage and snapshots Index, document ID, mapping, ingestion time, lifecycle policy, snapshot repository, and restore test
Shuffle Alert transport, orchestration, case creation, and approval flow Workflow revision, execution ID, node inputs and outputs, initiator, executor, retries, and redaction
TheHive Alerts, cases, observables, tasks, ownership, and investigation record External IDs, merge relationships, permissions, audit history, attachment integrity, and export semantics
Cortex Analyzer and responder execution Input evidence, analyzer version, external dependencies, report hash, responder authority, and output provenance
Velociraptor Targeted endpoint collection and hunt results Client identity, artifact definition, parameters, collection ID, server and client times, result hashes, and collection logs
Timesketch Timeline analysis, annotations, views, and collaborative interpretation Import source, timezone handling, parser version, event provenance, annotations, and sketch export verification

Wazuh can archive all received events in text or JSON when archive logging is enabled, while alerts are stored separately according to rule thresholds. That distinction matters. A case derived from a Wazuh alert should not imply that every underlying source event was retained unless archive configuration and retention prove it.

When a log platform exports search results, record the exact query, fields, time range, product edition and version, and preserve the exported bytes. An export is a projection of indexed data. It does not prove the input collector received every source event. Graylog Server is published under the SSPL, which is source-available rather than OSI-approved open source; edition, export behavior, and license terms must be checked for the selected release.

State the chain-of-custody limits

I reserve strong chain-of-custody language for a process that can answer who controlled an item, when it changed hands, where it was stored, what was done to it, and how integrity was checked.

State the chain-of-custody limits

An automated pipeline can produce excellent provenance without meeting every legal or organizational custody requirement. Its limits may include:

  • the source endpoint was not trusted at collection time
  • the SIEM retained alerts but not all raw events
  • source and collector clocks were not independently measured
  • a shared administrator group could alter the repository
  • no hardware-backed signing key or trusted timestamp was used
  • collection changed source state or an API export omitted inaccessible fields
  • legal-hold and replicated-cloud custody requirements remain unresolved

Write those limits into the case package. For routine response, I call the result a "documented evidence lineage with integrity checks." If legal proceedings are possible, involve counsel and trained forensic personnel early. A product audit log cannot decide admissibility.

Retention and export are part of design

Retention should be assigned at acquisition, then preserved through derived records. The case status alone should not decide when source evidence disappears.

Retention and export are part of design

A retention record needs:

retention:
  class: security-incident-high
  policy_version: 2026.2
  review_at: 2027-09-19T00:00:00Z
  disposition: review_then_delete
  legal_hold: false
  records_owner: governance
  derived_objects_inherit: true
  deletion_requires:
    - case_closed
    - hold_absent
    - records_owner_approval

Deletion is a custody event. Record who authorized it, which object versions were removed, which replicas or backups remain, and what the platform can and cannot prove about physical erasure.

An export package should be self-describing:

case-2026-0042/
  README.txt
  manifest.json
  manifest.sig
  custody-events.jsonl
  case/
    case.json
    tasks.json
    notes.json
    audit.jsonl
  evidence/
    ev-01JEXAMPLE/
      source.bin
      acquisition.json
      normalized.json
      transformations.jsonl
  attachments/
  timelines/
  tool-metadata/
    versions.json
    schemas/
    artifact-definitions/

README.txt should state the export time, exporting principal and workload, case scope, hash and signature algorithms, verification command or procedure, required software, redactions, omitted material, retention class, and known limits. Avoid a package that can only be opened by the exact production platform version that created it.

Test export and restore before an incident needs them. Open the package on an isolated system, recalculate hashes, import supported timelines, and confirm counts. A generated ZIP is not evidence of recoverability.

A case-promotion checklist

A case-promotion checklist

I use this gate when an alert becomes a case:

Source preservation

  • The source alert bytes or an explicitly documented API representation are preserved.
  • A unique evidence ID and source-native ID exist.
  • Byte length, media type, acquisition time, and SHA-256 are recorded.
  • Storage is versioned or immutable for the assigned retention period.
  • Collection completeness and errors are explicit.

Provenance

  • Source event, ingestion, alert, acquisition, and case times remain separate.
  • Original time strings and parser assumptions are retained.
  • Initiator, approver, executor, and custodian are distinct fields.
  • Rule, workflow, parser, analyzer, and artifact versions are recorded.
  • Each observable points to supporting evidence locations.
  • Analyst conclusions are separate from source observations.

Automation safety

  • Case creation uses a stable idempotency key.
  • Ambiguous timeouts trigger readback before retry.
  • Protected collections have target, approval, and concurrency gates.
  • Dry run shows collection scope, destination, impact, and retention.
  • Secrets and restricted fields are redacted before logging or transfer.

Integrity and verification

  • Hash coverage is stated for every object.
  • Transformations produce versioned receipts and list losses.
  • Destination objects are read back after creation or merge.
  • Attachments are downloaded and rehashed at a defined sampling rate or in full.
  • The signed manifest digest reaches an independent audit destination.

Retention and export

  • Retention class, policy version, owner, review date, and hold state are set.
  • Derived evidence inherits or explicitly overrides retention.
  • Export includes source, normalized, derived, and analyst-authored layers.
  • Export verification passes on an isolated system.
  • Chain-of-custody limits travel with the package.

Preserve the disagreement

A case is useful because it changes. Analysts refine scope, correct classifications, reject weak indicators, and add conclusions. The evidence chain should let that work happen without making the latest interpretation look like the only thing that ever existed.

Preserve the disagreement

I preserve original acquisitions, keep normalized data versioned, attach observables to their sources, and treat case actions as attributed state changes. Hashes protect defined bytes. Readback proves that destination systems hold what the workflow claims.

The most important limit is simple: provenance does not make a compromised source truthful. It shows what was collected, when, by whom or what, how it changed, and which integrity checks passed. That is enough to make incident work more reproducible and reviewable, provided I do not call it more than it is.

Read next