This module installs the analyst reflex that survives a fluent AI verdict: separate what the model quoted from what it inferred, and run the query that would disprove its conclusion before you disable anyone's account.
AI can compress the assembly work of triage — summarizing a noisy alert, pulling context from five tools, writing the KQL you half-remember — from twenty minutes to twenty seconds. This module teaches you to take that speed while treating every AI conclusion as a hypothesis you verify, because the failure mode that ends careers is not the AI that is obviously wrong — it is the one that is almost right.
Ananya is an L2 analyst on a managed-SOC team in Bengaluru, six years in, KQL and SPL fluent, running a shift for a retail-banking client.
Microsoft Sentinel fires incident INC-48213: Anomalous sign-in — impossible travel, user r.menon@client.example, Mumbai then Frankfurt in 41 minutes, followed by a mailbox forwarding-rule creation. The queue behind it has 60 more. Old-Ananya would spend twenty minutes here: read the raw SigninLogs, pivot to OfficeActivity, check the user's normal geo, decide.
She pastes the alert's structured fields — not the client's raw PII, just the entity graph and event types — into her team's triage assistant, an open model running against a Sentinel export.
Four seconds later: "High-confidence account compromise. Impossible-travel sign-in from Frankfurt (new ASN) immediately followed by attacker-typical persistence: an inbox forwarding rule to an external address. Recommend: disable user, revoke sessions, escalate to IR." Every claim carries a quoted log line. It reads clean. It reads escalate-now clean.
Here is the discipline the module is about. Ananya does not act on the verdict — she audits it. She clicks the citation behind "Frankfurt." The SigninLogs row is real, but the field the AI read as the login location is ResultType 50140 — an interrupt event for an MFA prompt, not a completed authentication. The Frankfurt "login" never succeeded.
The forwarding rule, she confirms in OfficeActivity, was created from Mumbai at 14:05 by the user herself, minutes before she filed a helpdesk ticket about "sorting invoices to a folder."
She runs a two-line KQL the assistant drafted — this time to disconfirm — checking whether any Frankfurt session ever reached ResultType 0. None did.
False positive. Impossible-travel triggered by a VPN egress node; the persistence "finding" was a user-created rule. She writes it up, tunes the correlation to require a successful auth before the persistence join, and moves on.
The AI had stapled two real events into one false story: a failed foreign MFA prompt plus a benign rule change, narrated as a compromise chain.
The cost of the other timeline. Had she trusted the fluent verdict, she would have disabled a working executive's account mid-quarter-close, opened a Sev-1 IR bridge, and burned the client's confidence — over an AI conclusion that was 70% correct facts wired into a 100% wrong story. The alert was real. The logs were real. The narrative was hallucinated, and narrative is what a tired analyst buys at 14:22.
Time saved by the AI on the assembly: about eighteen minutes. Time spent by Ananya on verification: about ninety seconds. That ratio — huge assembly savings, small verification tax — is the entire economic case for this module, and the verification is not optional overhead. It is the job.
Start with what the assistant actually returned above, reduced to its shape:
CLAIM: sign-in from Frankfurt (new ASN) → CITED to a real SigninLogs row
CLAIM: forwarding rule = attacker persistence → CITED to a real OfficeActivity row
CONCLUSION: account compromise, escalate
Both citations resolve to real events. The conclusion is still wrong, because the model supplied the causal link between them — and the causal link is exactly the part no log line supports. This is the "almost-right" failure mode, and it is more dangerous than an obvious hallucination precisely because it survives a glance.
The mitigation is a rule you apply to every AI verdict before you act on it: a claim you can check in seconds is safe; a conclusion that stitches claims together is a hypothesis until you test the stitch. Enrichment and summarization are checkable. Causation and verdicts are not — they are the AI's inference, and inference is where fluency outruns fact.
| AI output type | Trust posture | How you verify |
|---|---|---|
| Field extraction / normalization | Verify by sampling | Spot-check quoted values against the raw event |
| Summary of one event | Low risk | Does the summary omit or invert anything material? |
| Enrichment (asset, user, IOC context) | Verify the source | Is the context from a tool of record, or invented? |
| Cross-event correlation | Treat as hypothesis | Does a query confirm the causal link, not just the facts? |
| Verdict / recommended action | Never act unverified | Run a disconfirming query before you escalate or contain |
The dangerous AI verdict is not the one that is obviously wrong — it is the fluent one built from real citations wired into a false causal story. Verify the stitch between facts, not just the facts.
The practical habit is to ask the model to make verification easy for it and for you. Two prompt disciplines carry most of the safety: force verbatim citations, and ask for a disconfirming test alongside every conclusion.
You are triaging a SOC alert. For every factual claim, quote the exact log
line it comes from — no paraphrase. Separate FACTS (quoted) from INFERENCE
(your reasoning). For your top hypothesis, give me ONE query I can run that
would DISPROVE it if it were false. Do not recommend a containment action.
The "disprove it" instruction is the single most valuable line in a triage prompt. A model asked to confirm its own verdict will find support; a model asked to hand you the query that would kill its verdict gives you the exact test Ananya ran at 14:31.
Here is enrichment done right, as structured output so the brief is machine-postable to your case system:
import anthropic
from pydantic import BaseModel
class Enrichment(BaseModel):
entity: str
fact: str # one atomic claim
source_quote: str # verbatim from provided context ONLY
source_tool: str # which tool of record this came from
class TriageBrief(BaseModel):
incident_id: str
summary: str
enrichments: list[Enrichment]
open_questions: list[str] # what the model could NOT ground and a human must check
client = anthropic.Anthropic()
response = client.messages.parse(
model="claude-opus-4-8",
max_tokens=3000,
thinking={"type": "adaptive"},
messages=[{
"role": "user",
"content": f"""Enrich this alert from the provided context only.
<alert>{alert_fields}</alert>
<signin_logs>{signin_rows}</signin_logs>
<asset_context>{cmdb_rows}</asset_context>
<threat_intel>{ti_matches}</threat_intel>
Every enrichment MUST quote its source verbatim and name the tool. If a
detail is not in the context above, put it in open_questions — do NOT
supply it from general knowledge.""",
}],
output_format=TriageBrief,
)
brief = response.parsed_output
The load-bearing instruction is "if a detail is not in the context above, put it in open_questions — do not supply it from general knowledge." Without it, an LLM asked to enrich a Frankfurt IP will happily tell you the ASN, the hosting provider, and a plausible-sounding threat-actor association — some of it real, some of it confabulated, none of it distinguishable at a glance. Grounding the model in the pasted context and forcing an explicit "I don't know" channel is what converts enrichment from a liability into a time-saver.
The open_questions field carries as much weight as the enrichments themselves. It is the model's channel for "the asset owner is not in the CMDB export you gave me" or "no threat-intel match for this hash in the provided feed" — the honest gaps a human then fills. A brief with an empty open-questions list on a genuinely under-sourced alert is a warning sign, not a clean bill: it usually means the model papered over what it did not know rather than admitting it.
You can build this over any stack. The vendor-neutral pattern is the same whether the retrieval side is Microsoft Sentinel with Security Copilot, Google SecOps with Gemini, or a local model over Elastic and Splunk exports — the model reads tool-of-record context and cites it; it never sources facts from its own weights. The tool of record supplies the facts; the model only arranges and cites them, and the moment it starts supplying facts of its own, enrichment has become fabrication with good formatting.
Enrichment is safe only when it is grounded — every enriched fact quotes a tool of record, and anything the model cannot ground goes to an explicit open-questions list instead of being invented.
Query generation is where AI pays off daily and misleads quietly. Ask for a hunt in English and you get KQL or SPL in seconds:
// "Show sign-ins for r.menon in the last 24h that actually succeeded,
// with location and the result type" -> generated KQL
SigninLogs
| where TimeGenerated > ago(24h)
| where UserPrincipalName == "r.menon@client.example"
| where ResultType == 0 // 0 = success; NOT an interrupt/failure
| project TimeGenerated, Location, IPAddress, ResultType, AppDisplayName
| order by TimeGenerated asc
The quiet failure is not a syntax error — a broken query throws, and you notice. The dangerous output is a query that runs and returns a confident, wrong number: a hallucinated field name that silently matches nothing, a ResultType filter the model got backwards, a join that drops rows. An empty result set from a subtly wrong query reads exactly like "no malicious activity found," and that is how a real detection gets closed as clean.
The rule is: AI writes the query; the schema validates it, and you sanity-check the count against a known quantity before you believe a zero. Run it against a window where you know the answer, confirm every field exists in the table schema, and treat "zero results" from an AI-authored query as unproven until you have seen the same query return the rows it should on a positive control.
| Query-gen risk | What it looks like | Guard |
|---|---|---|
| Hallucinated field | where SrcIp == ... when the column is IPAddress | Validate every field against table schema before running |
| Inverted logic | ResultType == 0 meant to catch failures | Test on a window with a known answer |
| Silent empty set | Runs clean, returns 0 rows | Positive control: does it find the event you planted? |
| Over-broad join | Correlates on a field shared by benign events | Read the join key; is it specific to the behavior? |
Never trust a zero from an AI-generated query until you have watched the same query return rows on a case you know is positive — an empty result from a subtly broken query is indistinguishable from "all clear."
Half of investigation is reading a log format you have never seen: a firewall syslog dialect, a custom appliance's JSON, a Base64 blob in a UserAgent. AI is genuinely excellent here, and the guardrail is a hard line — you ask it to explain and decode, never to run.
Here is a raw log line from an appliance I don't recognize. Tell me: the
vendor/format if identifiable, what each field means, and decode any encoded
values so I can read them. If a field is Base64 or hex, DECODE and SHOW the
plaintext — do NOT execute it, do NOT construct a command, do NOT fetch any
URL you find. This is read-only artifact analysis.
<log>{unfamiliar_line}</log>
Decoding a Base64 payload to read what it contains is core DFIR work and entirely safe. The line you never cross — in a prompt or in your own head — is turning that decoded content into an action: running the command it spells out, curling the URL it points to, pasting it into a live shell. The model deobfuscates so you can understand the artifact; understanding is the deliverable, execution is a different and far more dangerous activity that belongs in an isolated sandbox under separate controls, covered in M3.
Deobfuscation and artifact analysis are read-only — the AI decodes so you can read the payload; it never runs, fetches, or constructs anything executable from what it finds.
Alert fatigue is a data problem, and you have the data: a month of a noisy rule's alerts, each with entities, timestamps, and what the analyst did next. Export it and hand it to the model with a clustering job:
Here are 900 impossible-travel alerts from the last 30 days. Group them by
ROOT CAUSE, not by user. For each cluster give me: the shared underlying
reason (one VPN egress ASN? one misconfigured service account? one roaming
executive?), how many alerts it accounts for, and whether suppressing it
would blind us to a real compromise of that same entity. Rank clusters by
volume. Do not propose a suppression that would hide a successful auth.
The output is a tuning worklist, not a verdict: "612 alerts — one VPN egress ASN in Frankfurt; 190 — a backup service account that roams between two datacenters; 98 — genuine roaming users; 0 — anything with a successful foreign auth." That is the VPN pattern behind Ananya's false positive, made visible across the whole month. You still decide which clusters to suppress and how to narrow the rule — the AI turned 900 rows into four reasons, but the judgment about what is safe to silence stays yours, because the wrong suppression is how a real intrusion gets tuned into invisibility.
The measure of success is precision: the fraction of that rule's alerts that led to real action, before and after. A rule whose alerts are 95% ignorable is not 95% effective — it is 0% effective on the day the real one lands in the noise, which is the argument for treating alert precision as a number you track, not a feeling you have.
At end of shift, the assistant drafts case notes from the incident's own timeline and your actions: what fired, what you found, what you verified, and what is still open for the next analyst. Here the trust bar is different from a verdict — a human reads the note before anyone acts on it — but the byline rule still holds absolutely.
The analyst who worked the case owns every claim in the note. A fluent handoff that says "confirmed benign" where you only suspected benign becomes the next shift's false memory, and false memory in a case record is how a downgraded incident quietly stays downgraded. Instruct the model to distinguish what you verified from what you assumed, and to carry your open questions forward verbatim rather than resolving them with a confident guess.
The safe uses of AI in triage are the ones a human reads before action — summarization, clustering, handoff drafts; the unsafe ones are where AI output becomes action directly. Know which side of that line any given use falls on.
The draft removes the blank page, which is the reason most handoffs are thin; the ownership keeps the record true, which is the reason a handoff is worth reading. Skip the ownership and you have automated the production of confident, unverified case notes — the opposite of what a handoff is for.
One India-specific constraint governs all of the above for managed-SOC teams. You often cannot paste a client's raw logs, code, or PII into a public AI tool — the client contract and DPDP obligations forbid it. Three moves keep the workflow billable:
DPDP awareness also sets your incident clock. When triage confirms a personal-data breach, two timers start and you must reconcile them: CERT-In's 6-hour reporting rule and the Data Protection Board's 72-hour rule. Knowing which alerts can trip these obligations is part of triage judgment now, not just an afterthought for the GRC team — an impossible-travel alert on a customer-facing identity is not only a security question, it is potentially the start of a statutory reporting clock, and the analyst who recognizes that at 14:22 saves the org a scramble at hour five.
The redaction habit is not a tax on the AI workflow — it is what makes the workflow legal, and it is cheap. Ananya sent the entity graph and event types, never the raw records, and got a full-fidelity triage brief anyway, because the model needed the shape of the events to reason, not the customer's actual name or account number. Data minimization before prompting is the discipline that lets a services SOC use AI at all, and it is a habit worth building before the tooling makes it easy to forget.
This module anchors LAB-01 (alert-triage copilot with a verification gate) and LAB-02 (NL→KQL/SPL query assistant): build a triage pipeline over a canned SIEM export that summarizes and proposes a verdict but is forced through a citation/verification rubric, and a query assistant that auto-validates generated KQL/SPL against a sample log set. The artifacts — a false-conclusion catch log showing where you overrode the AI, and a hallucinated-field rejection log — are the strongest interview evidence this module produces: anyone can say "I use AI in the SOC"; you can show the moment it was almost-right and you caught it.
Your first 15 minutes: before you act on any AI triage verdict this week, click one citation behind its conclusion — if the cited log line does not itself prove the causal link, treat the verdict as an untested hypothesis, not a finding.
Check yourself: An AI triage assistant returns a "high-confidence account compromise" verdict in which every factual claim is quoted to a real log line. Why is that not enough to act on — and what specifically do you verify before you contain?
Companion playbooks — take these into your week alongside LAB-01 and LAB-02: pb-01 (Triage a High-Volume Alert Queue Through a Verification Gate), pb-02 (Generate SIEM Queries from Plain-English Hunts), and pb-04 (Audit an AI Triage Rationale to Catch Its Wrong Conclusions).