flowchart LR
A["<b>1. Init</b><br/>triggers, baseline tags,<br/>trigger source"] --> B["<b>2. Collect</b><br/>fetch email,<br/>get case details,<br/>get similar cases"]
B --> C["<b>3. Transform</b><br/>extract IOCs,<br/>create entities<br/>(URL, IP, HOSTNAME, FILEHASH)"]
C --> D["<b>4. Operate</b><br/>enrich entities,<br/>mark suspicious,<br/>evaluate conditions on scope"]
D --> E["<b>5. Conclude</b><br/>set priority, tag,<br/>change stage,<br/>assign to T2"]
I spent a few days building a T1 triage playbook for phishing alerts in Google SecOps SOAR, or Chronicle SOAR as I call it. The detections were already in place: Gmail’s “Report Phishing” button and the KnowBe4 plugin both produce alerts I wanted to handle automatically. The plan was the usual T1 work: collect context, run enrichment, tag obvious false positives, hand off to T2.
I started building it the way I’d build anything: like a Python script. Extract IOCs from the email, loop over them, call VirusTotal on each, tag the case if anything came back malicious. Same pattern I’d reach for in any automation problem: request, parse, branch, repeat.
Halfway through, I realized I was burning hours on plumbing. Every “for each IOC” loop in Chronicle SOAR is a flow block with its own context, and every “if this field is malicious” became a nested condition reading from a JSON path. I had four parallel branches doing near-identical work. It functioned, but it was ugly, and I knew I wouldn’t want to extend it in three months. I was driving a screw with a hammer.
The Entity Model
SOAR platforms aren’t imperative scripting environments. They’re closer to data pipelines with a standard intermediate representation. I’m working with Chronicle SOAR specifically, but the pattern generalizes. In Chronicle’s case, that representation is the entity: a typed, deduplicated object the platform tracks across cases and over time. URLs, IP addresses, hashes, hostnames, users; all IOC types fit here.
Once data is in entity form, every action knows how to operate on it. VirusTotal_v3 - Enrich URL doesn’t need a loop; point it at “All URL entities” and it fans out automatically, in parallel. Same for Enrich IP, Enrich Hash, the WHOIS power-up, and most marketplace integrations. Conditions can evaluate [Entity.IsSuspicious] across all entities in scope with the contains operator. No iteration or JSON path gymnastics.
The implication is that the value of the platform is in transforming raw data into entities, then operating on entities declaratively. The minute you start parsing JSON arrays of IOC-shaped data inside playbook conditions, you’re fighting the tool.
The Five-Stage Pattern
I sketched out the structure I should have started with, like a framework for this kind of playbook. Five stages, in order:
Each stage has a clear job and a clear handoff:
- Init. Triggers, baseline tags, metrics, custom fields. Cheap boilerplate.
- Collect. Fetch all data you’ll need downstream: email message, case details, run queries in other tools like EDR, get similar cases, user context. Run in parallel where you can, and don’t make any decisions yet, just gather.
- Transform. This is the conceptual pivot. Process raw data, extracting IOCs, email attachments, and paths, separating interesting pieces of data. Then, process this data, calculating hashes, saving files to case, etc. You’ll want to identify the Entities. Finally, convert them into entities using
Create Entityactions with the right delimiter, scoped to the right type. After this stage, the raw data is mostly irrelevant; the entity graph is what matters. - Operate. Run enrichment functions over entity scopes.
Enrich URLonAll URL,Enrich HashonAll FILEHASH. Mark entities as suspicious via configured thresholds, and use a single condition on[Entity.IsSuspicious]to consolidate the verdict. No for-loops or nested ifs. The platform handles parallelism for free. - Conclude. Based on information processed, set priority, drop tags, change stage, assign to T2. The decisions here read from the work done in stage 4, so they should be small and obvious.
When the playbook is shaped this way, stage 4 reads less like code and more like a sentence: “for every URL we found, check VirusTotal; for every IP, check VirusTotal; if anything came back malicious, escalate.” That’s the goal: readability over cleverness.
Reusable Blocks
The next refinement, and one I’m still working into my own playbooks: stages 1, 4, and 5 shouldn’t be simple stacked actions. They should be lists of block invocations.
Blocks in Chronicle SOAR are reusable sub-playbooks, basically functions. You define VT_Enrich_All_Entities once, with the enrichment actions, thresholds, and post-conditions, and call it from any playbook that needs it. Same for KB4_Simulation_Check, Sender_Reputation, Similar_Cases_Sweep, and Attachment_Hash_And_Enrich.
If you look at the official Google playbooks shipped via the Marketplace or in the adoption guides, this is exactly the shape. A T1 playbook isn’t forty stacked actions; it’s a trigger, an init step, and four or five block calls, each owning a coherent piece of logic. The block library does the work, and the playbook just orchestrates.
A reasonable starter library for a phishing triage T1:
Identity_Enrichment: pull user context (department, manager, recent reports) for the reporter.Threat_Data_Enrichment: VT, WHOIS, URLScan over all entities in scope. Setsis_suspicious.Attachment_Handling: guarded by a “has attachment” condition. Hashes, createsFILEHASH, enriches.Similar_Cases_Sweep: find prior cases with the same message ID, sender, or subject hash. Critical for catching campaigns.Verdict_And_Tag: read the consolidated entity state, drop the right tags, set priority.
The orchestration playbook becomes maybe ten boxes total. The complexity lives in the blocks, where it can be tested and versioned independently.
This pays off in two places:
- Maintenance. A change to enrichment logic (new threshold, new engine allowlist) happens in one block, not five playbooks.
- Composition. The next playbook becomes a thirty-minute job of arranging existing blocks, not a week of copying actions.
A Familiar Shape
What I described is the same shape as well-known patterns in software engineering:
- Pipes and filters. Stages transform a stream, each one reading the previous stage’s output shape. Adding a new transformation doesn’t require rewriting earlier stages.
- ETL. Extract → Transform → Load. My stages 2 → 3 → 4 are literally ETL with security-flavored names.
- Functional operations over collections.
mapandfilterinstead of explicit loops. Chronicle’s entity-scoped actions aremapover a typed collection, exactly the same idea dressed up in a flowchart UI. - OODA / NIST IR lifecycle. Observe → Orient → Decide → Act. Triage playbooks are OODA with checkboxes.
All of these converge on the same shape because the alternative (raw data threaded through the whole flow, imperative branches everywhere) doesn’t compose. Add a new IOC type or a new enrichment source and you rewrite half the playbook. The pipeline shape absorbs new types for free.
Where AI Fits
Google and other vendors have packaged what they call “agentic triage”: an LLM in the loop, calling enrichment functions, producing a verdict and a confidence score. One step that replaces most of the pipeline above. I’ve tested it. It works.
The instinct to drop it into the front of the SOC funnel is wrong, and the reason isn’t cost; though cost matters too. Three problems, roughly in order of how much they should worry you.
- The real risks are prompt injection and non-determinism. Phishing emails are, by construction, untrusted input written by someone trying to deceive the recipient. Feeding that body to an LLM with tool access (mark malicious, escalate, close as benign) is handing the adversary a function-call interface. Mitigations exist (system-prompt hardening, content sanitization, tool-output validation) but none of them are airtight. The second-order problem is that two runs of the same alert can produce different verdicts, which makes audit, regression testing, and analyst trust harder than they need to be.
- Cost scales the wrong way for T1. Deterministic pipelines have flat per-alert cost: a VT lookup is a VT lookup. LLM pipelines scale with alert volume and with prompt size. T1 is exactly where alert volume is highest and per-alert value is lowest. Putting the most expensive component at the widest part of the funnel is a budget killer.
- AI’s edge is judgment, not lookups. An LLM is not a faster VT client. Where it earns its tokens is synthesis: reading the email body, the reporter’s recent activity, similar past cases, and producing a short narrative a T2 can audit in thirty seconds. That capability is wasted on alerts an
[Entity.IsSuspicious]condition already resolved.
Two concrete placements fall out:
- Authoring, not runtime. Use an LLM to draft playbooks, suggest blocks, sketch the five-stage skeleton. Cost is paid once, at design time, and the model is out of the loop before the first alert hits production. The number of alerts processed never touches the bill.
- Downstream of cheap triage, scoped narrowly. After deterministic stages have ruled out the obvious, run the agentic step on what survives — medium and high priority only, and even then scoped to specific detection rules or alert categories where narrative synthesis actually helps. Fewer alerts, higher per-alert value, both budget and judgment line up.
What you should not do is let the LLM be the only thing between an alert and a decision. Beyond cost and injection, you’ve built a single point of failure with no fallback: if the provider degrades, if a model update silently regresses, or if the agent’s tool loop gets stuck, every alert backs up behind it.
Closing Thoughts
The lesson, which I had to learn the slow way: when you pick up a new tool, the cost isn’t learning the syntax, it’s unlearning the habits you brought with you from a tool that worked differently. I built playbooks the way I build Python scripts because that’s the shape my brain reaches for. The platform was giving me an entity model, parallel scoping, declarative conditions, and reusable blocks, and I was ignoring all of it to write nested for-loops in a flowchart UI.
Adapt the work to the tool you have, not the other way around. Recalling Maslow’s Hammer, “I suppose it is tempting, if the only tool you have is a hammer, to treat everything as if it were a nail.” If the playbook is fighting the platform, the platform is going to win, usually six months later when you need to extend it and find you can’t. ✌️
References
- Create a fallback playbook. Google’s official guide to T1 design principles. Short and direct; the “non-destructive, information-gathering” framing is the same as my stage 4.
- Create an enrichment playbook block. The canonical reference for blocks, with a good walkthrough of how to design one that handles multiple entity types.
- Adoption Guide: Designing and Building Your First Playbooks. Google Cloud Community walkthrough of the exact pattern with concrete actions and block names.
- Adoption Guide: Building Effective Playbooks. Covers entity scoping and the
[Entity.IsSuspicious]condition in depth. - Foresite: Advanced Tips for Google SecOps SOAR Automation. Has the “One Playbook to Rule Them All” tip, which extends this pattern further toward a universal master playbook.
Reuse
Citation
@online{lopes2026,
author = {Lopes, Joe},
title = {SOAR {Playbooks} {Aren’t} {Scripts}},
date = {2026-05-29},
url = {https://lopes.id/log/soar-playbooks-scripts/},
langid = {en}
}