Malspark

Concepts

Evidence ledger

Every tool call a run makes is written down with a citable id, and every claim in the report points back to one.

The evidence ledger is the record of every tool call a run made. Each entry has an id such as ev_0094. Agents cite these ids in their claims, the judge cites them in the verdict, and the report cites them in every section. A reader can open any citation and see exactly what the tool was asked and what it answered.

How an entry is made

The tool loop wraps every tool. When an agent calls one, the loop times the call, records the arguments and the answer, and hands the answer back to the model with the entry's id on the front:

[ev_0094]
{"0x1360bc0b344": "int FUN_1360bc0b344(undefined8 *param_1) { ... }", ...}

The model sees the id with the answer, so it can cite it. The entry holds the answer whole, exactly as the model received it.

Ids count up across the whole run. The triage pack's calls come first, under the agent pipeline. The judge's own calls and one agent's question to another go through the same recorder, so a verdict that leans on them can cite them too.

What an entry holds

This is ev_0094 from the benchmark run, as GET /api/v1/jobs/{job_id}/evidence returns it, shortened:

{
  "entry_id": "ev_0094",
  "stage": "reversing",
  "agent": "all_tools_reverser_ghidra",
  "tool": "batch_decompile",
  "source": "provider:ghidra",
  "model": "anthropic/claude-haiku-5-5",
  "ok": true,
  "duration_ms": 474,
  "args": {"program": null, "functions": "0x1360bc0378c,0x1360bc0cf14"},
  "output": "{\"0x1360bc0378c\":\"...bool FUN_1360bc0378c(...) ...\"}",
  "truncated": false,
  "repeated_of": null,
  "facts": {"v": 1, "kind": "decompile_batch", "effects": {"network": false, "executes": "no", "third_party": "no"}, "arguments": {"program": "program"}},
  "digest": "94745b5d…",
  "chain": "75a595bd…"
}
FieldMeaning
entry_idThe id a claim cites.
stage, agentWho made the call, and in which stage.
tool, server, sourceWhich tool answered, and how it was reached: a tool server, a static provider, the pack.
modelThe model whose turn asked for the call. An agent can fall back to another model mid-run, so this is recorded per call.
ok, error, remediationWhether the call succeeded. A failed call is still an entry, with the error and what would fix it.
truncatedSet when a per-agent byte budget dropped the output. The call is kept and counted.
repeated_ofSet when the same call was made again and answered from the earlier entry.
factsWhat the platform knows about the tool: the kind of answer, and whether it executes the sample, uses the network or reaches a third party.
digest, chainThe seal. See below.

How claims use it

An analyst's claim names the entries it rests on. This is one of the triage agent's claims from the same run, as the stored findings hold it:

{
  "claim": "The decoded strings include system-discovery command lines: ipconfig /all, systeminfo, ver, whoami /groups, net config workstation, net view /all /domain, nltest /domain_trusts (and /all_trusts), net group \"Domain Admins\" /domain, and the WMI query for AntiVirusProduct. Code refers to these strings through the command routines. This is evidence of intent to run them, not of their execution.",
  "evidence_ref": "[ev_0019] [ev_0021]",
  "confidence": 0.75,
  "technique_id": "T1033"
}

ev_0019 is the pack's FLOSS answer and ev_0021 its decoded string blobs. The command lines the claim names are decoded strings in them.

Code checks every citation. A claim that cites no entry, cites one that does not exist, or cites one whose answer does not hold the value the claim states is sent back to the analyst with a validation code such as isr.ungrounded_technique or report.citation_wrong_entry. The analyst gets a turn to fix it.

The report turns citations into numbered notes. Each note lists its entries, and the evidence bundle beside the report prints every entry the notes name.

Sealed when stored

When the run is stored, the worker seals the ledger. Each entry gets digest, a SHA-256 of everything stored for it, and chain, an HMAC that links it to the entry before. The chain head is written to the audit chain beside the report's own digest. A changed, removed or reordered entry, or a changed report, fails verification.

curl -s http://localhost:8000/api/v1/jobs/$JOB/evidence/verify \
  -H "Authorization: Bearer $TOKEN"

An enrichment that changes a sealed report later appends an amendment. Sealed rows are never rewritten. See Sealed evidence ledger.

Reading the ledger

WhereWhat
The console's Evidence → RecordsEvery entry, filterable by stage, agent and tool. A citation chip in the report opens its entry.
GET /api/v1/jobs/{job_id}/evidenceThe entries, filterable by stage, agent and tool.
GET /api/v1/jobs/{job_id}/evidence/call-mapWhich call led to which.
GET /api/v1/jobs/{job_id}/evidence/verifyChecks the seal.
GET /api/v1/reports/{report_id}/evidence-bundle.htmlThe report's notes, each with the entries it cites.
GET /api/v1/jobs/{job_id}/packageThe whole run as one archive. See Job package.

The full entry format is in Architecture.

Edit on GitHub

On this page