File size: 10,570 Bytes
1c16318 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 | """
Stage 4 - Analysing the output.
Per-window verdicts in, two reports out: one row per window, and one consolidated document
verdict.
The consolidation is where the honest work happens. Three things have to be got right:
1. **Overlapping windows are not independent findings.** Windows overlap by 50%, so one payload
normally lands in two of them. Counting flagged windows would report a single injection as
two. Adjacent flagged windows are merged into regions, and regions are what "in how many parts"
counts.
2. **A window verdict is not a document verdict.** Part B measured Gemma on one window per
document at a 3% false-alarm rate. A 40-window document asks the same question 40 times, so
isolated single-window hits deserve less confidence than a region two or more windows wide.
That is reported rather than silently thresholded away.
3. **The family is a suggestion.** Gemma's family accuracy is 0.630 overall and 0.00 on `ssrf`.
The consolidated report says so, every time, next to the family it is suggesting.
"""
from collections import Counter
# Straight from the evaluation card, so the app cannot quote a score without its baseline.
MEASURED = {
"gemma": {"f1": 0.969, "precision": 0.993, "recall": 0.946,
"false_alarm_rate": 0.03, "family_acc": 0.630},
"qwen": {"f1": 0.957, "precision": 0.995, "recall": 0.921,
"false_alarm_rate": 0.02, "family_acc": 0.524},
"mimo": {"f1": 0.945, "precision": 0.988, "recall": 0.906,
"false_alarm_rate": 0.05, "family_acc": 0.433},
"phi": {"f1": 0.900, "precision": 0.824, "recall": 0.991,
"false_alarm_rate": 0.95, "family_acc": 0.330},
}
BASELINE_F1 = 0.900 # "always malicious" - the bar every number above must be read against
WEAK_FAMILIES = {"ssrf"} # families the winner catches but cannot name (0.00 in Part B)
def merge_regions(findings: list) -> list:
"""
Collapse flagged windows into regions of the document.
Two flagged windows belong to the same region when their character ranges touch or overlap.
Each region carries the windows that produced it, the family they voted for, and the single
best evidence string among them.
"""
flagged = [f for f in findings if f["pred_injected"] == 1]
if not flagged:
return []
flagged.sort(key=lambda f: f["start"])
regions, current = [], [flagged[0]]
for f in flagged[1:]:
if f["start"] <= current[-1]["end"]: # touching or overlapping
current.append(f)
else:
regions.append(current)
current = [f]
regions.append(current)
out = []
for group in regions:
votes = Counter(f["pred_family"] for f in group if f["pred_family"] != "none")
family, family_votes = (votes.most_common(1)[0] if votes else ("none", 0))
# The longest quoted evidence in the region: the model that had most to point at.
best = max(group, key=lambda f: len(f["evidence"] or ""))
out.append({
"start": group[0]["start"],
"end": max(f["end"] for f in group),
"windows": [f["index"] for f in group],
"n_windows": len(group),
"family": family,
"family_agreement": round(family_votes / len(group), 2) if group else 0.0,
"evidence": best["evidence"],
"evidence_offset": _locate(best),
"reasoning": best["reasoning"],
})
return out
def _locate(finding: dict):
"""Character offset of the quoted evidence in the whole document, or None if it is not there."""
ev = finding.get("evidence") or ""
if not ev:
return None
i = (finding.get("text") or "").find(ev)
return None if i < 0 else finding["start"] + i
def consolidate(findings: list, doc: dict, model: str = "gemma") -> dict:
"""The document-level verdict, built only from what the windows actually said."""
regions = merge_regions(findings)
n_flagged = sum(f["pred_injected"] for f in findings)
unreadable = sum(1 for f in findings if not f["parse_ok"])
families = Counter(r["family"] for r in regions if r["family"] != "none")
# A region seen in two or more overlapping windows is a stronger signal than a lone hit: the
# model was shown the same bytes twice, framed differently, and said yes both times.
corroborated = [r for r in regions if r["n_windows"] >= 2]
return {
"injected": bool(regions),
"model": model,
"n_regions": len(regions),
"n_corroborated": len(corroborated),
"n_windows_flagged": n_flagged,
"n_windows": len(findings),
"unreadable": unreadable,
"families": families.most_common(),
"regions": regions,
"seconds": round(sum(f.get("seconds", 0) for f in findings), 1),
"document": doc,
}
# --------------------------------------------------------------------------------------------
# Rendering
# --------------------------------------------------------------------------------------------
def window_rows(findings: list) -> list:
"""
One row per window, for the per-window table in the GUI.
The last column is what the model actually said, trimmed. It is here because "unreadable" is
the one verdict a reader cannot check: every other row can be compared against the document,
while a window that failed to parse offers nothing to look at unless the raw answer is shown.
With it, a run of unreadable windows says which failure it is - an unfinished <think> block,
an answer cut off mid-quote, or plain prose that never reached a verdict.
"""
return [[f["index"],
f"{f['start']:,}-{f['end']:,}",
"INJECTED" if f["pred_injected"] else "clean",
f["pred_family"] if f["pred_injected"] else "-",
(f["evidence"] or "")[:90].replace("\n", " "),
f["parsed_by"],
(f.get("raw") or "")[:160].replace("\n", " ⏎ ")]
for f in findings]
WINDOW_COLUMNS = ["window", "characters", "verdict", "family", "evidence", "answer read as",
"what the model said"]
def report_markdown(summary: dict) -> str:
"""The consolidated report."""
doc = summary["document"]
m = MEASURED.get(summary["model"], {})
lines = []
if summary["injected"]:
lines.append(f"# Injection found - {summary['n_regions']} "
f"{'region' if summary['n_regions'] == 1 else 'regions'} of the document")
else:
lines.append("# No injection found")
lines.append("")
lines.append(f"**{summary['n_windows_flagged']} of {summary['n_windows']} windows flagged** "
f"| {doc['chars']:,} characters extracted from {doc['file_bytes']:,} bytes "
f"| {summary['seconds']}s on {summary['model']}")
if doc["was_truncated"]:
lines.append("")
lines.append("> **This document was truncated to 120,000 characters** (head and tail kept, "
"middle elided) - the same budget the corpus was built with. An injection in "
"the elided middle would not be seen.")
if summary["unreadable"]:
lines.append("")
lines.append(f"> **{summary['unreadable']} of {summary['n_windows']} windows produced an "
f"answer that could not be read**, and each counts as *clean*. Roughly one in "
f"seven is normal for this model; a much higher rate means this verdict rests "
f"on less evidence than the window count suggests.")
if summary["injected"]:
lines += ["", "## What was found", "",
"| region | characters | windows | family | agreement | evidence |",
"|---|---|---|---|---|---|"]
for i, r in enumerate(summary["regions"], 1):
ev = (r["evidence"] or "-")[:70].replace("\n", " ").replace("|", "\\|")
where = f"@{r['evidence_offset']:,}" if r["evidence_offset"] is not None else ""
lines.append(f"| {i} | {r['start']:,}-{r['end']:,} | {r['n_windows']} | "
f"`{r['family']}` | {r['family_agreement']:.0%} | `{ev}` {where} |")
if summary["families"]:
named = ", ".join(f"`{f}` x{n}" for f, n in summary["families"])
lines += ["", f"**Families suggested:** {named}"]
lines += ["", f"**Corroboration:** {summary['n_corroborated']} of "
f"{summary['n_regions']} regions were flagged in two or more overlapping "
f"windows. A region seen once is a weaker signal than one seen twice."]
weak = [f for f, _ in summary["families"] if f in WEAK_FAMILIES]
if weak:
lines += ["", f"> The suggested family {', '.join('`'+w+'`' for w in weak)} is one this "
f"model **never names correctly** (0.00 in the evaluation). Treat the "
f"detection as real and the label as unreliable."]
else:
lines += ["", "Every window was scored and none reported an injection. This is a negative "
"result from a model with recall "
f"{m.get('recall', 0):.3f} on the evaluation corpus - it misses roughly "
f"{1 - m.get('recall', 0):.0%} of the attacks it is shown."]
lines += ["", "---", "", "## How much this verdict is worth", "",
f"On 1,100 held-out PDFs this model scored **F1 {m.get('f1', 0):.3f}**, against "
f"**{BASELINE_F1:.3f} for a detector that flags everything without reading it**. "
f"That is a {(m.get('f1', 0) / BASELINE_F1 - 1):.1%} relative improvement, not an "
f"order of magnitude.",
"",
f"- **Precision {m.get('precision', 0):.3f}**, false alarms on "
f"{m.get('false_alarm_rate', 0):.0%} of clean files.",
f"- **Family named correctly {m.get('family_acc', 0):.3f} of the time** - the family "
f"above is a suggestion, not a verdict.",
"- Measured on synthetic injections of harmless test markers (EICAR/AMTSO/WICAR/"
"RANSIM) into ordinary PDFs. It is **not** a general malware scanner.",
]
return "\n".join(lines) |