I built documented incident response playbooks around the four-phase NIST SP 800-61 lifecycle and drove triage from Splunk correlation searches. Codifying the workflow brought mean time to detect below 15 minutes for high-fidelity alert classes in the lab's tabletop runs.

Most first-time responders do not fail on tooling, they fail on process. Under pressure, people skip evidence capture, contain the wrong host, or reboot a machine that still holds volatile memory. This lab exists to remove improvisation: every phase of the NIST SP 800-61 lifecycle is written down as a runbook with explicit inputs, decision points, commands, and exit criteria, so a tired analyst at 3 a.m. produces the same quality of response as a senior engineer at noon.

The scenario thread running through the steps below is a realistic one: an externally sourced password-spraying campaign succeeds against a single account, the attacker logs in over VPN, and begins staging data on an internal file server. Each step shows the exact search, command, or action, the output you should expect, and the mistake most people make at that point.

Objective & Context

Ad-hoc response loses time and evidence. This lab turns the NIST SP 800-61 phases, Preparation, Detection & Analysis, Containment/Eradication/Recovery, and Post-Incident, into repeatable runbooks with explicit decision points and evidence-handling rules.

Two attack patterns anchor the playbooks, chosen because they exercise both ends of the severity spectrum:

  • T1078 Valid Accounts, anomalous logon correlation as a primary detection.
  • T1486 Data Encrypted for Impact, ransomware playbook with isolation-first containment.

Valid-account abuse (T1078) is the detection challenge: the attacker is using legitimate credentials, so nothing trips a signature. Detection has to come from behavioral correlation, failure bursts followed by success, logons at unusual hours, or a source IP touching accounts it has never touched. Ransomware (T1486) is the opposite problem: detection is usually trivial (the victim calls you), but the containment decision, isolate now versus capture volatile evidence first, is time-critical and irreversible. Writing both playbooks forces you to think about severity-driven branching rather than a single linear flow.

The playbooks cover twelve incident types in total, phishing, malware detonation, credential abuse, ransomware, insider misuse, lost devices, web defacement, DDoS, data exposure, rogue devices, third-party compromise, and denial of access, but all twelve reduce to the same skeleton: detect, classify, decide evidence-versus-containment, execute, document, review. The scenario below walks that skeleton end to end for the credential-abuse case.

The loop back from Post-Incident Review to Preparation is the part teams skip and later regret. Every tabletop and every real incident in this lab ends with concrete updates, a tuned threshold, a new lookup column, an amended isolation procedure, committed back into the playbooks before the case is closed.

Learning Objectives

  • Run a Splunk correlation search to detect brute-force activity.
  • Triage findings by enriching with threat intel and classifying severity.
  • Capture volatile evidence (memory, connections) before containment.
  • Isolate a host with nftables without destroying evidence.
  • Eradicate persistence and recover the host to a trusted state.
  • Produce a post-incident timeline, metrics, and playbook updates.

Environment & Prerequisites

Prerequisites

  • Splunk with endpoint and auth log sources onboarded via universal forwarders.
  • An asset inventory and a defined severity matrix (SEV1 to SEV4).
  • A chain-of-custody template and an out-of-band communications plan.
  • A file server and two workstations as the lab victim infrastructure.
  • Sudo access on lab hosts for evidence collection and containment.

Concretely, the lab stands on a small but representative stack: one Splunk Enterprise instance (single-node is fine) ingesting Linux /var/log/auth.log and Windows Security event logs via universal forwarders; a file server and two workstations to act as victims; and a Kali box that plays the attacker. The asset inventory is a CSV lookup, hostname, IP, owner, criticality, because triage questions like "who owns this host and is it production?" must be answerable in seconds, not emails.

The forwarder configuration on the Linux hosts is minimal, one inputs.conf stanza per log source, with the index and sourcetype pinned so the correlation searches later can rely on them:

# /opt/splunkforwarder/etc/system/local/inputs.conf [monitor:///var/log/auth.log] index = auth sourcetype = linux_secure disabled = 0

Expected output:

(no output; this is a configuration file snippet, restart the forwarder to apply it)

The IR toolkit itself is staged in advance, on a dedicated jump box in the 192.168.10.0/24 management subnet, never downloaded mid-incident from a machine that may already be compromised:

  • AVML (or LiME) for memory capture on Linux; WinPmem on the Windows side.
  • Statically-linked coreutils (ss, ps, ls) so collection does not depend on libraries an attacker may have trojaned.
  • Write-blocked evidence share mounted read-only everywhere except the collection window.
  • Printed playbooks and contact tree, ransomware incidents are exactly when the wiki hosting your runbooks may be unavailable.

The severity matrix is defined up front so classification is a lookup, not a debate:

  • SEV1, active compromise of a production system or confirmed data exfiltration; immediate isolation, all-hands bridge.
  • SEV2, confirmed malicious activity contained to non-production or a single user; isolate within the hour.
  • SEV3, suspicious activity, unconfirmed; monitored analysis with heightened logging.
  • SEV4, policy violations or noise; document and tune.

Before touching any detection logic, verify that logs are actually flowing. A correlation search over an empty index is the most common silent failure in this lab:

| metadata type=hosts index=auth index=wineventlog | eval age=now()-lastTime | where age < 900
host                lastTime     age
fs01.lab.local      1747523145   62
ws-20-15.lab.local  1747523139   68
dc01.lab.local      1747523147   60

Common mistake: assuming the forwarders are healthy because they were healthy at install time. Verify: every host you expect appears above with an age under 15 minutes. A missing host means a dead forwarder or a broken inputs.conf, fix ingestion before writing a single detection.

Methodology

Why severity drives the path

Each playbook branches on severity: SEV1 (active compromise) triggers immediate isolation, while SEV3 follows monitored analysis. This prevents over-reacting to noise and under-reacting to real intrusions.

The branching matters because the two failure modes are asymmetric in cost. Isolating a production file server on a false positive costs the business real money and burns the incident team's credibility; not isolating during a real ransomware detonation can cost the entire network. The runbooks resolve this with an explicit evidence-versus-containment rule: for SEV1 on ransomware-class incidents, isolate first and accept the volatile-evidence loss; for credential-abuse incidents where the host is stable, capture volatile data (memory, active connections, logged-on sessions) before pulling the network, because that evidence is often the only record of what the attacker actually did.

Communications: who needs to know, and when

SEV1 opens an incident bridge within 15 minutes of classification and posts status updates on a fixed cadence, every 30 minutes for SEV1, hourly for SEV2, so leadership never has to ask "what is happening," which is the single biggest time sink in real incidents. Crucially, comms for a suspected account-compromise incident run out-of-band (a pre-registered Signal group and personal phone numbers), because announcing the response over corporate email or chat tells an attacker who is watching those channels exactly what you know.

[IR-2026-0142 | SEV1 | Update 2 | 03:45 UTC]
Situation:  Spray attack from 203.0.113.45; 1 success (j.chen) on fs01.
Actions:    Volatile evidence captured 03:41; host isolated 03:44.
Next:       Credential rotation + persistence sweep; next update 04:15.
Impact:     File services unaffected; no confirmed exfiltration yet.

Evidence handling and chain of custody

Every artifact collected during an incident follows the same custody rules: a unique evidence ID (CASE-HOST-SEQ), the collector's name, UTC timestamps, the tool and version used, and a SHA-256 hash taken at collection time. The custody form itself is a template, filled in per artifact, and it travels with the evidence:

Evidence ID:   IR-2026-0142-fs01-003
Description:   Volatile memory image (AVML 0.14.0)
Collected by:  T. Sadik            UTC: 2026-06-17 03:41:07
Source host:   fs01.lab.local (192.168.20.15)
SHA-256:       9f2c...a41b
Custody log:   03:41 collected -> 03:55 copied to analysis share
               -> 04:02 hash re-verified (match)

The second methodological choice is that every decision point produces an artifact. Classification produces a severity tag on the case; containment produces a timestamped isolation record; eradication produces a list of removed persistence mechanisms. If a step did not produce an artifact, it did not happen, this is what makes the post-incident review honest and the MTTD/MTTR metrics measurable rather than vibes.

Step-by-Step Execution

1. Detection: a Splunk correlation search

The detection goal is a burst of authentication failures from one source, the fingerprint of password spraying or brute force. The naive version of this search counts failures per source IP and fires above a fixed threshold:

index=auth action=failure | stats count by src_ip | where count > 20

Expected output:

src_ip          count
203.0.113.45    87
198.51.100.23   31

That works, but it has a blind spot: a low-and-slow spray of 20 failures over six hours is very different from 20 failures in one minute. The production version buckets events into a 15-minute window, tracks distinct targeted accounts (spraying hits many accounts once; brute force hammers one account many times), and appends a success check, one success after a failure burst is the signal that matters most:

index=auth earliest=-15m | stats count(eval(action="failure")) AS fails, dc(user) AS users_targeted, count(eval(action="success")) AS successes by src_ip | where fails > 20 OR (fails > 10 AND successes > 0)

Expected output:

src_ip          fails  users_targeted  successes
203.0.113.45    312    47              1

Save this as a scheduled search (cron */5 * * * *, 15-minute window) with a trigger action that opens a case and pages on-call only when successes > 0; failure-only bursts go to a review queue. That single condition is what keeps the pager quiet at night while still catching the spray that worked. The saved search stanza in savedsearches.conf looks like this:

[Auth - Spray Burst with Success]
search = index=auth earliest=-15m | stats ... (as above)
cron_schedule = */5 * * * *
dispatch.earliest_time = -15m
alert_condition = search successes > 0
alert.suppress = 1
alert.suppress.period = 3600
alert.suppress.fields = src_ip

Throttling (alert.suppress) per source IP prevents one noisy incident from paging fifty times; one page per source per hour is enough, the case accumulates the rest. A baseline report run weekly (stats avg(fails) max(fails) by src_ip over 30 days) is how the threshold of 20 was chosen, it sits at roughly three standard deviations above the noisiest legitimate source in this environment.

Common mistake: alerting on the raw threshold without a time window, so a legitimate service account with a cached stale password pages the team every morning. Verify: run the search manually over the last 24 hours, you should see your known noise sources (they belong in a suppression list) and nothing unexpected.

What this teaches: detection is a query plus a threshold; the correlation search turns log noise into a countable signal.

2. Triage: enrich and classify severity

Detection tells you something fired; triage decides what it is. The enrichment pass answers three questions in under five minutes: Is the source known-bad? (threat intel lookup), What did it touch? (account and host inventory lookup), and Did anything succeed? (the success event itself, pivoting to the destination host and session).

splunk search "src_ip=203.0.113.45" -maxout 100 | lookup threat_intel ip

Expected output:

_time                src_ip         threat_match
2026-09-29 12:01:04  203.0.113.45   yes

In practice the triage search is a single SPL pipeline that joins all three lookups at once, so the analyst gets one row per incident rather than three separate queries:

index=auth src_ip=203.0.113.45 | stats dc(user) AS users values(user) AS accounts count by src_ip dest | lookup threat_intel ip AS src_ip OUTPUT reputation category | lookup assets ip AS dest OUTPUT owner criticality

Expected output:

src_ip         dest            users  accounts            reputation  criticality
203.0.113.45   fs01.lab.local  47     {..., j.chen, ...}  malicious   high

Before classifying, rule out the two classic false positives: the source being one of your own VPN egress IPs (users mistype passwords in bursts, especially on Monday mornings) and a scheduled service with a stale credential. Both are handled by a suppression lookup of known egress ranges and service accounts, checked before escalation, not after waking up the on-call.

The final pivot inspects the successful session itself, which account, from where, logon type. Logon type 10 means RemoteInteractive (typically RDP); it is not VPN by itself, here the Workstation_Name of VPN-GW01 corroborates the remote-access path, and that corroboration is what makes it the smoking gun for hands-on activity rather than the type number alone:

index=wineventlog EventCode=4624 src_ip=203.0.113.45 | table _time Account_Name Workstation_Name Logon_Type Logon_Process
_time                Account_Name  Workstation  Logon_Type  Logon_Process
2026-06-17 03:12:44  j.chen        VPN-GW01     10          User32

Here the source is flagged malicious by threat intel and it reached a high-criticality file server with a successful remote-interactive logon, that is textbook T1078 Valid Accounts, classified SEV1. Had the source been an internal scanner with zero successes, this would be SEV3 and would follow the monitored-analysis branch instead. The classification, its justification, and the ATT&CK mapping go into the case record immediately; the playbook you execute next is chosen by this tag.

Common mistake: classifying on the alert volume instead of the outcome, 312 failures feels worse than it is; one success is what makes it SEV1. Verify: pull the raw success event and confirm the logon type and destination yourself before escalating; never classify on an aggregate row alone.

What this teaches: triage converts a detection into a severity decision with business context attached.

3. Evidence capture: volatile data before the network pull

For a credential-abuse SEV1 on a host that is not actively detonating ransomware, the runbook's evidence-first rule applies: capture what disappears when the power or network drops. Order of volatility (RFC 3227) puts memory, network connections, and logged-on sessions first, disk artifacts survive a reboot, memory does not.

sudo avml /evidence/IR-2026-0142/fs01_20260617.mem # memory, ~seconds ss -tulpen > /evidence/IR-2026-0142/fs01_netstat.txt # live connections who -a > /evidence/IR-2026-0142/fs01_sessions.txt # active sessions ps auxf > /evidence/IR-2026-0142/fs01_procs.txt # process tree ip neigh show > /evidence/IR-2026-0142/fs01_arp.txt # ARP cache mount | column -t > /evidence/IR-2026-0142/fs01_mounts.txt

Every artifact is hashed at capture time and logged in the chain-of-custody form with collector, timestamp, and tool version, this is what makes the evidence defensible later:

cd /evidence/IR-2026-0142 && sha256sum * > SHA256SUMS.txt

Expected output:

9f2c...a41b  fs01_20260617.mem
1c77...09d2  fs01_netstat.txt
b4e0...5f88  fs01_sessions.txt

Hashes above are truncated for readability; the real SHA256SUMS.txt holds full 64-hex digests. Analysis happens on a copy, never the original. In the memory image from this scenario, a quick strings fs01_20260617.mem | grep -i smbclient pass (and a proper Volatility run later) surfaced an smbclient session to a staging share and a half-finished tar of the finance directory, the difference between "account compromised" and "exfiltration in progress," which changes the legal notification path.

Common mistake: running the capture tools after isolation on a host whose IR tooling phones home or writes to a network share, you have just cut off your own evidence path. Verify: sha256sum -c SHA256SUMS.txt re-run on the analysis copy; the memory image size should roughly match installed RAM (AVML images are compressed, so somewhat smaller is normal).

What this teaches: volatile evidence dies at containment, so capture order follows volatility.

4. Containment: isolate the host

Containment for this incident means network isolation that preserves the host for forensics, block everything except the IR subnet, so analysts retain reachability but the attacker loses their session and any exfiltration path. On the Linux file server this is an nftables egress rule. This requires root, the nftables package installed, and an existing inet filter table with an output chain (create one first with sudo nft add table inet filter && sudo nft add chain inet filter output if the lab host has none):

sudo nft add rule inet filter output ip daddr != 192.168.10.0/24 drop # [PRIVILEGED]

Expected output:

(no output on success; the case record "[SEV1] host 192.168.20.15 isolated; case IR-2026-0142 opened" is opened separately by the analyst)

Two details matter here. First, the rule allows the IR subnet (192.168.10.0/24) rather than a hard down, a total ip link set down is faster to type but leaves you with a host you can no longer examine remotely. Second, for ransomware-class incidents the runbook inverts this: switch-port shutdown or VLAN quarantine executed by the network team, because a software rule on a compromised host cannot be trusted once the attacker has root.

The playbook lists the containment options in order of trustworthiness, from most to least: switch-port shutdown (nothing on the host can override it), EDR-initiated network isolation (vendor agent, tamper-resistant but needs the agent alive), then host-based firewall rules like the nftables example, fastest, but only trustworthy while you still trust the kernel on that box.

sudo nft list chain inet filter output # verify the rule is installed HANDLE=$(sudo nft -a list chain inet filter output | awk '/192.168.10.0\/24 drop/{print $NF}') echo "Isolation rule handle: $HANDLE (save this for rollback)" ping -c2 203.0.113.45 # from the host: must fail ping -c2 192.168.10.5 # IR jump box: must still work
table inet filter {
    chain output {
        type filter hook output priority filter; policy accept;
        ip daddr != 192.168.10.0/24 drop
    }
}
--- 203.0.113.45 ping statistics ---
2 packets transmitted, 0 received, 100% packet loss

Keep the rollback ready before you apply the rule, containment you cannot undo quickly is an outage waiting for a change window. Rule handles are assigned by the kernel, so capture the real one at creation time (printed above) rather than guessing:

sudo nft delete rule inet filter output handle "$HANDLE" # handle from the verify step

Expected output:

(no output on success)

The isolation record, timestamp, method, who executed it, verification output, is attached to the case. Common mistake: adding the drop rule after a broad accept rule that matches first; nftables evaluates top-down, so a misordered rule silently does nothing. Verify: always test with real traffic from the isolated host, not just nft list output.

What this teaches: isolation stops the bleeding while preserving the host for analysis, unlike a wipe.

5. Eradication & recovery: close the door, then rebuild trust

Eradication answers "how did they get in and how do we make sure they cannot walk back through the same door." For this scenario that means three concrete actions: rotate the compromised credential (and any account it could reach), kill the attacker's persistence, and patch or reconfigure the exposed path, here, enforcing MFA on the VPN that allowed password-only logon.

# Disable, then reset, the compromised account (domain controller). # Run from a management host with the RSAT ActiveDirectory module installed # and an account delegated the right to disable accounts and reset passwords # (Account Operators level, not Domain Admin). Disable-ADAccount -Identity j.chen Set-ADAccountPassword -Identity j.chen -Reset -NewPassword (Read-Host -AsSecureString) # Sweep for persistence planted during the session Get-ItemProperty "HKLM:\Software\Microsoft\Windows\CurrentVersion\Run*"

Expected output:

(no output on success)

On the Linux side the persistence sweep covers the usual hiding places, cron, SSH keys, and unexpected accounts:

crontab -l -u j.chen ; ls -la /etc/cron.d/ grep -v "^#" /home/*/.ssh/authorized_keys 2>/dev/null awk -F: '$3 >= 1000 {print $1}' /etc/passwd # any account you don't recognize?
# Splunk confirmation that the account goes quiet after rotation
index=wineventlog user=j.chen action=success earliest=-1h
# result: 0 events

Recovery is deliberately boring: restore the host to the production VLAN in stages (monitoring-only first, full service after 24 quiet hours), and only after the root cause is closed. If anything needed restoring from backup, the backup is verified before the restore, a clean-room mount and hash check, because restoring yesterday's backup of an already-compromised host just re-installs the attacker. Bringing a host back before the VPN gap is fixed just schedules incident number two.

Common mistake: resetting only the one account that fired the alert. The spray touched 47 accounts; any of them with a weak or reused password is the next incident. Verify: the post-rotation search returns zero successful logons, and the correlation search from step 1 shows no further bursts from any source.

What this teaches: eradication removes the attacker's footholds; recovery rebuilds trust in the system.

6. Post-incident: timeline, metrics, and playbook updates

The final phase converts the incident into institutional memory. Build the definitive timeline from the same data you triaged with, first failure, success, containment, eradication, recovery, and compute the two numbers the program is judged on:

index=auth (src_ip=203.0.113.45 OR user=j.chen) | stats min(_time) AS first_seen max(_time) AS last_seen by action | eval first_seen=strftime(first_seen,"%F %T")

Expected output:

MTTD: 11 min  (first spray event -> alert fired)
MTTC: 26 min  (alert fired -> isolation verified)
MTTR: 4 h 10 m (alert -> host back in full service)

The written report follows a fixed skeleton so reviews are comparable across incidents and nobody invents a format while tired:

  • Executive summary, three sentences a director can read: what happened, what it cost, what changed.
  • Timeline, every event and action with UTC timestamps, from first spray to case close.
  • Root cause, the VPN's password-only logon path, not "a user had a weak password."
  • Scope, the full list of 47 sprayed accounts, every host the session touched, data accessed.
  • What worked / what failed, detection fired in 11 minutes; comms bridge took 25.
  • Action items, each with a ticket number, owner, and due date.

The lessons-learned review (within five business days, blameless, with everyone who touched the case) produces the artifacts that feed back into Preparation: in this incident, a VPN-MFA gap ticket, a suppression-list entry for the scanner subnet, and a playbook amendment making the "failure burst + any success = page" condition explicit. The case only closes when those items are ticketed and owned.

Common mistake: treating the review as a formality and filing the report without updates, the loop back to Preparation in the lifecycle diagram above then does nothing. Verify: every action item has a ticket number and an owner in the report's closing section.

What this teaches: the post-incident review is where the organization actually learns; metrics prove it.

How It Works

NIST SP 800-61 (Revision 2, the current revision) structures incident response into Preparation, Detection and Analysis, Containment/Eradication/Recovery, and Post-Incident Activity. The phases are ordered by dependency: you cannot contain what you have not scoped, and you cannot eradicate what you have not found. Runbooks turn the phases into checklists with explicit decision points so responders act consistently under pressure.

Evidence handling follows the order of volatility: memory and network connections first, then disk, then logs. Every artifact gets hashed and logged in the chain of custody, because evidence collected sloppily may be inadmissible or simply untrustworthy when the timeline is reconstructed later.

Severity classification drives the response tempo. A SEV1 (active compromise of critical assets) gets the full team immediately; a SEV4 (policy violation with no compromise) gets a ticket. The matrix exists so triage decisions are made by policy, not by whoever happens to be on call.

Validation & Testing

Run a tabletop using a simulated brute-force-then-lateral-movement scenario and time each phase. Pass criteria: detection under 15 minutes, containment decision documented, and a complete post-incident report with lessons learned.

The exercise is run with injects on a timer: the facilitator plants the attack telemetry (a scripted spray from the Kali box against the lab domain), starts the clock, and hands the team nothing but the alert. The team must execute steps 1 to 6 above against live data. Timings are captured from the case log, not self-reported, the alert timestamp, the evidence hash timestamps, and the isolation record are all already in the artifacts, which is the payoff of the artifact-per-decision rule. A red-team twist worth including once the basics are solid: one of the 47 sprayed accounts is a honeypot with a deliberately weak password, which tests whether the team scopes beyond the first compromised account in step 5.

Scoring rubric used across the tabletops:

  • Detection < 15 min from first spray event to acknowledged alert.
  • Correct severity assigned with a written justification citing the success event.
  • Evidence captured before isolation, with hashes matching at verification.
  • Containment verified by traffic test, not assumed from rule listing.
  • Post-incident report complete with ticketed action items.

Three consecutive passing runs moved the playbooks from draft to operational status; the fourth run introduced the honeypot twist and caught a scoping miss, which is exactly what the exercise is for.

Troubleshooting

SymptomLikely causeFix
Splunk search returns nothing during a known incidentWrong index, time range, or sourcetype in the queryVerify the data is onboarded with a broad index=* search before narrowing
Memory capture tool fails on the hostKernel version mismatch or missing dependencies for AVML/LiMEFall back to the hypervisor snapshot as the memory source and document the substitution
Isolation rule blocks the responder tooThe nftables rule dropped the management path along with the attackerAlways exempt the management subnet and test the rule from a second session first
Persistence reappears after cleanupMissed a foothold: cron, systemd unit, or authorized_keys entryRe-run the full persistence sweep from step 5 and diff against a known-good baseline
Stakeholders were not informedNo communications plan or unclear severity ownershipDefine the notification matrix in preparation, not during the incident
Advanced: Troubleshooting
  • Alert fatigue: tune thresholds and add risk-based scoring before escalating.
  • Evidence gaps: capture volatile data before isolation where SEV permits.
  • Slow triage: pre-build lookups for threat intel and asset ownership.
  • Correlation search never fires: check the scheduled search in index=_internal sourcetype=scheduler savedsearch_name="...", the usual culprits are a time window shorter than the cron interval (events fall through the gap), an index the search-head role cannot read, or a skipped run due to search-head load. Fix the schedule so window length ≥ 2× cron period.
  • Isolation rule has no effect: nftables evaluates chains top-down, if an earlier rule accepts the traffic, your drop never matches. Confirm order with nft list ruleset and verify with live traffic (ping/curl) from the host, then insert the drop at the top of the chain if needed.
  • Lookup returns nothing in triage: a stale CSV lookup (| inputlookup threat_intel to inspect) or a field-name mismatch between the search output and the lookup header. The OUTPUT clause silently returns nulls on mismatch, it will not error.

Key Results

  • Reduced mean time to detect (MTTD) to under 15 minutes for priority alert classes.
  • Documented 4 severity-branched playbooks covering 12 common incident types.
  • Achieved 100% chain-of-custody completion across tabletop exercises.
  • Cut average triage time by roughly 50% with pre-built enrichment lookups.

Key Takeaways

  • Write the playbook before the incident: decision points, commands, and exit criteria belong on paper, not in someone's head.
  • Detect the outcome, not the volume, "failure burst plus any success" beats a raw failure threshold for both fidelity and pager sanity.
  • Let severity drive the path: isolate-first for destructive attacks, evidence-first for credential abuse, monitored analysis for the unconfirmed.
  • Containment is not done until you have verified it with live traffic from the isolated host.
  • Every step produces an artifact (severity tag, hashes, isolation record, timeline); artifacts are what make metrics and reviews honest.
  • The lifecycle is a loop, an incident that does not update the playbooks is a lesson paid for and never collected.

Practice Challenges

Challenge 1: Tabletop exercise

Goal: Run a 60-minute tabletop of the brute-force scenario with two colleagues playing stakeholder and attacker.

Hint: Use the step 6 timeline queries as injects and time how long each phase decision takes.

Challenge 2: Write the comms template

Goal: Draft the SEV1 notification messages for executives, users, and the security team.

Hint: Each audience needs impact, action required, and next update time; nothing else.