Security Operations

What Is SOAR?

HuntRule Team · · 10 min read

A row of railway signal levers in a dark signal box, all locked upright except one held mid-throw by a gloved hand
On this page

POST /devices/entities/devices-actions/v2?action_name=contain takes a CrowdStrike host off the network. action_name=lift_containment puts it back. Both are one HTTP call against the same endpoint, and that symmetry is the entire reason network containment is the easiest response action to automate.

SOAR means security orchestration, automation and response. Vendors sell it as one product, which hides the fact that the three words carry very different amounts of risk. Orchestration is plumbing. Automation is a scheduling decision. Response is the part that can take a payroll run offline at 3am.

Splitting the acronym

Orchestration is calling other systems. Your platform holds credentials for the EDR, the IdP, the SIEM, the ticketing system and the CMDB, and it knows each one's schema and rate limits. This is unglamorous and it is where most of the value sits. An analyst who no longer opens six consoles to answer "is this host a domain controller" gets that time back on every alert.

Automation is doing the orchestrated thing without a human in the loop. It is a separate decision from orchestration. You can orchestrate everything and automate nothing, and plenty of working SOCs do exactly that for the first year.

Response is the subset of actions that change state outside the security stack. Isolating a host, disabling an account, blocking an IP at the perimeter, purging a message from every mailbox. A rollback does not undo these, because the user already missed the meeting.

Automating a broken triage process does not fix it. It runs the same wrong answers at a higher volume and a lower latency.

What automates cleanly

The safe list has a property in common: every action is read-only or trivially reversible.

  • Enrichment. Hash reputation, domain age, ASN ownership, asset owner, patch level. Watch the quotas. The VirusTotal public API is capped at 4 requests per minute and 500 per day, and it is explicitly not licensed for commercial workflows, so a naive per-alert lookup either throttles or breaches terms. Cache by hash for 24 hours.

  • Deduplication. Group by a stable key before anything else runs. Host plus source image plus calendar day kills most of the duplicate volume for endpoint rules.

  • Case creation and ticketing. Deterministic, and idempotent if you key on the dedupe hash.

  • Notification. Paging the on-call rota from the CMDB owner field, not a hardcoded channel that was correct in 2023.

  • Reversible containment. Defender for Endpoint isolation is POST https://api.security.microsoft.com/api/machines/{id}/isolate with a body of {"Comment": "...", "IsolationType": "Full"}, and the undo is the same URL with /unisolate and only a Comment. Both are limited to 100 calls per minute and 1,500 per hour, a ceiling worth knowing before a noisy rule tries to isolate a subnet.

Enrichment carries almost no blast radius. Enrich thirty alerts wrongly and you have thirty alerts with bad context. Contain thirty hosts wrongly and you have an outage.

What should not be fully automatic

Disabling an account is the standard worked example, and it deserves the detail.

At 3am a credential access rule fires on a host. The account that triggered it is svc-backup-prod. A playbook with no gate calls PATCH https://graph.microsoft.com/v1.0/users/{id} with {"accountEnabled": false}, the backup fleet loses its identity, and the failure surfaces four hours later as a restore that cannot authenticate. Nobody is awake to correlate the two. The security team has performed T1531 on its own estate.

The reversible cousin is worth reaching for first. POST /users/{id}/revokeSignInSessions invalidates refresh tokens and browser session cookies by resetting signInSessionsValidFromDateTime to now. It does not delete the account. It forces a re-authentication. Two caveats from the Microsoft documentation change how you write the playbook around it: there is a delay of a few minutes before tokens are actually revoked, and it does not revoke sessions for external users, because those sign in through their home tenant.

The on-premises equivalent has its own gap. Disabling an account in Active Directory does not claw back a Kerberos TGT that was already issued, and the default maximum user ticket lifetime is 10 hours. A playbook that disables an account and reports "contained" is overstating what happened.

Same reasoning applies to perimeter IP blocks (the address is often your own egress NAT), mailbox purges (discovery obligations), and process termination on a domain controller.

Playbooks are code, and nobody treats them like it

This is the part vendor material skips. A playbook is a program with network I/O, branching, credentials and side effects. It needs everything code needs.

Version control, so you can answer "what changed on Tuesday" when case volume halves overnight. Tests, including a dry-run mode that walks every branch against recorded API responses and asserts on the actions it would have taken. Error handling, because a 429 from VirusTotal and a 500 from the EDR need different behaviour. Timeouts on every call, because a playbook that hangs on enrichment never reaches its containment step.

Integrations break quietly. A vendor renames a JSON field from verdict to classification in a minor API version. Your if result["verdict"] == "malicious" branch raises a KeyError, or worse, your defensive .get("verdict", "clean") returns clean forever. The playbook keeps running. The dashboard stays green. Every malicious hash scores benign, and you find out during the incident the automation was supposed to prevent.

The test is not "does the playbook run". It is "when did this branch last execute". Instrument branch counters and alert when a branch that normally fires daily goes quiet.

A lifebuoy still bolted to its wall bracket, the rescue rope beneath it rotted through and hanging in frayed pieces

A worked playbook

Reconstructed pseudo-code for an LSASS handle alert (T1003.001), the kind fired by Sysmon Event ID 10 with TargetImage of lsass.exe and a GrantedAccess value such as 0x1010 or 0x1410. Note where it stops on its own and where it stops for a person.

playbook: credential-access-lsass-handle
trigger:  rule HR-WIN-LSASS-HANDLE  (Sysmon EID 10, TargetImage endswith lsass.exe)

1. dedupe
   key = sha256(host_id + SourceImage + calendar_day)
   if open case with same key -> append event, exit.

2. enrich   (read-only, parallel, 30s timeout each, failures are non-fatal)
   EDR   process tree for SourceProcessId, signer, first-seen-in-org
   VT    hash reputation, 24h cache, skip on 429 and record skipped=true
   IdP   accounts with sessions on this host in the last 8h
   CMDB  host role, owner, criticality, account_type for each account

3. decide
   signer in org_allowlist AND SourceImage in approved_tooling
        -> close as expected, tag, exit.
   VT skipped OR CMDB unreachable
        -> queue for tier 1 with a partial-enrichment banner, exit.
   signer unsigned OR first_seen_in_org < 7d OR host.role == "domain_controller"
        -> continue to 4.
   otherwise
        -> queue for tier 1, exit.

4. automatic, reversible only
   EDR   POST /api/machines/{id}/isolate {"Comment":"<case>","IsolationType":"Full"}
   IdP   POST /users/{upn}/revokeSignInSessions   for each account from 2
   collect volatile data: process list, netstat, autoruns, scheduled tasks
   open Sev 2 case, page IR on-call
   record every call: endpoint, status code, response id

5. HUMAN GATE   (no timeout, no auto-approve on expiry)
   proposed:  disable {upn}, force password reset
   shows:     account_type (human | service | managed_identity),
              CMDB owner, hosts this account authenticated to in 24h,
              blast radius if disabled
   requires:  one approval from IR on-call
   if account_type != "human": two approvals, one from the service owner

6. on approval
   PATCH /users/{id} {"accountEnabled": false}
   write action, actor, approver, timestamp and raw response to the case

Step 3's second branch is the one people leave out. A playbook that treats a failed enrichment as a clean result is worse than one with no enrichment at all, because the analyst trusts the banner-free case.

Step 5 has no timeout escalation on purpose. "Auto-approve if nobody responds in 15 minutes" is a human gate on paper and full automation in practice, because the 3am case is exactly the one nobody responds to. What belongs above that gate and what belongs below it is decided in the detection playbook the automation implements.

Detect your own automation

Your SOAR service account is a privileged identity that performs destructive actions on a schedule. Monitor it like one. A burst of Windows event 4725 from the automation account means either a real campaign or a playbook stuck in a loop, and both need waking someone.

title: Account disabled by SOAR service account
name: account_disabled_by_soar
status: experimental
description: Records account disable actions performed by the automation service account.
logsource:
    product: windows
    service: security
detection:
    selection:
        EventID: 4725
        SubjectUserName|startswith: 'svc-soar'
    condition: selection
falsepositives:
    - Scheduled joiner-mover-leaver automation sharing the same service account
level: informational
tags:
    - attack.impact
    - attack.t1531

That rule alone is noise. Pair it with a correlation so only the burst pages anyone.

title: Burst of automated account disables
status: experimental
correlation:
    type: event_count
    rules:
        - account_disabled_by_soar
    group-by:
        - SubjectUserName
    timespan: 10m
    condition:
        gte: 5
level: high

What it catches: a runaway playbook, a compromised SOAR credential, and an approval gate that somebody removed to clear a backlog. What it misses: the single wrong disable, which is the common case and which no threshold finds. It also misses anything the SOAR does through a different identity, so pin the service account naming convention before deploying this. Known false positive: HR offboarding batches, which look identical. Apply the same pattern to isolation actions, with the threshold set below the platform's own rate limit.

Automation amplifies detection quality in both directions

A rule that fires 40 times a day at a 90 percent false positive rate is a nuisance in a queue. Somebody closes 36 cases and grumbles. Wire that rule to automatic host isolation and it is 36 wrongful isolations a day, which is a service desk incident, an executive question, and a lasting loss of trust in the security team's ability to touch production.

The prerequisite for SOAR is not the platform. It is detection content with a known false positive rate, measured per rule, over weeks of production data. Until you have that number for a rule, that rule gets enrichment and case creation and nothing else.

Summary

SOAR is three capabilities sold as one. Orchestration and enrichment automate cleanly and carry almost no blast radius. Response actions split into reversible ones you can automate behind a rate limit and irreversible ones that need a human approval gate with no timeout escape hatch. Playbooks are code and rot like code, silently, so version them, test every branch and alert when a branch stops firing. A noisy rule with an automated response is worse than a noisy rule alone.

Technique

ID

Where it appears

OS Credential Dumping: LSASS Memory

T1003.001

The alert the worked playbook responds to

Valid Accounts

T1078

The service account the playbook nearly disables

Account Manipulation

T1098

Changes to the SOAR service account's own privileges

Account Access Removal

T1531

What an ungated disable action does to your own estate

Events to collect before you automate anything that touches identity:

Windows Security  4624   Successful logon (which accounts were live on the host)
Windows Security  4672   Special privileges assigned to new logon
Windows Security  4720   User account created
Windows Security  4725   User account disabled
Windows Security  4728   Member added to security-enabled global group
Sysmon            10     ProcessAccess, TargetImage lsass.exe
Entra ID          SignInLogs + AuditLogs (Disable account, Revoke sessions)
EDR audit trail   isolate / unisolate actions with actor and case reference

Identity is where ungated automation does the most damage, so it is where the detection content has to be strongest first. Browse the identity rules at /rules?domain=identity, or work through the whole catalog at /rules.

Related articles