Blue Team of One field notes · security

HomeSOC OperationsSOC automation

SOC automation · enrichment

Turning threat intel into detections: auto-populating a Sentinel watchlist

An analyst confirms an IP is malicious, pastes it into a note, and moves on — and the next connection from that same IP sails past unremarked because nothing was watching for it. This closes that loop: a flow that writes known-bad IPs to a Sentinel watchlist, so every future hit lights up automatically.

Threat intel that lives in an analyst's head — or a closed incident, or a spreadsheet — isn't a detection. For a bad IP to actually defend you, it has to be somewhere your detection rules can see it, continuously, without a human re-checking. In Microsoft Sentinel, that place is a watchlist, and the way to keep it current without manual toil is a flow that populates it automatically.

01Why a watchlist

A named list your KQL can join against, every time a rule runs.

A Sentinel watchlist is a named, queryable table of entities — here, IP addresses — that you reference from analytics rules and hunting queries. Once an IP is on the list, any rule that joins telemetry against it will fire on a match, on every run, with no further human involvement. That's the difference between "we knew that IP was bad" and "we detect that IP whenever it comes back."

// an analytics rule joins live telemetry against the watchlist
let badIPs = _GetWatchlist('BadIPs') | project SearchKey = tostring(IPAddress);
SigninLogs
| where TimeGenerated > ago(1h)
| where IPAddress in (badIPs)
| project TimeGenerated, UserPrincipalName, IPAddress, AppDisplayName, ResultType

Swap SigninLogs for CommonSecurityLog, DeviceNetworkEvents, or any table with a source/destination IP and the same watchlist protects that surface too — one list, many detections.

02The automation loop

A flow that turns "an IP was flagged" into "the watchlist now contains it."

The flow (Power Automate or a Logic App — the steps are the same) sits between your source of bad IPs and the watchlist. Sensible triggers, depending on where your intel comes from:

TriggerSource of the IP
New Sentinel incident / alertThe IP entity on a triaged malicious incident
Scheduled recurrenceA pull from an external TI feed / API
Analyst submission (form / Teams / email)A human-confirmed bad IP, submitted deliberately
# the flow, step by step
1. Trigger        (new incident / schedule / analyst form submission)
2. Extract IP(s)  (from the incident entities or the feed payload)
3. Validate       (is it a well-formed public IP? drop RFC1918 / junk)
4. De-dupe        (read current watchlist; skip IPs already present)
5. Append         (add new IPs to the watchlist via the Sentinel API)
6. Notify         (post to the SOC channel: "N new IPs added")

Validate and de-dupe, or the list rots

Steps 3 and 4 are what keep the watchlist trustworthy. Without validation, a malformed or private IP (or your own egress IP pulled from a noisy feed) lands on the list and generates false positives forever. Without de-dup, the list bloats with repeats and can hit size limits. Add a source and date-added column so you can age entries out and trace where each came from.

03Closing the loop

Known-bad becomes always-detected.

With the flow running, the enrichment loop closes: the moment an IP is confirmed malicious — by an analyst, an incident, or a feed — it's on the watchlist within minutes, and from then on every analytics rule that references the list will alert on traffic to or from it. You've converted a one-time human observation into a standing, automated detection. The analyst's judgement is captured once and applied forever, across every log source you join against.

Why this matters operationally

The value isn't any single IP — it's removing the human from the repeat. The first time an IP is seen, a person decides it's bad. After that, the watchlist decides, instantly, on every subsequent appearance, at machine speed. That's the core move of SOC automation: keep humans for judgement, hand the repetition to the flow.

04Housekeeping

A watchlist you never prune becomes a liability.

Two ongoing tasks keep this healthy. First, aging: IPs go stale — an address that was a malicious VPS last month may be a legitimate service today, so use the date-added column to expire entries after a defined window (a second scheduled flow can prune them). Second, allow-listing: guard against ever adding your own egress IPs, partner ranges, or Microsoft service IPs, which would generate a flood of false positives. A small static exclusion list checked in step 3 prevents the worst self-inflicted incidents.

The takeaway

A bad IP only defends you if your detections can see it continuously — so put it in a Sentinel watchlist and keep the list current with a flow: trigger on a confirmed-bad IP, validate it, de-dup, append, notify. Every analytics rule that joins against the list then detects that IP automatically, forever. Add source + date columns, age stale entries out, and exclude your own ranges. Humans for judgement; the flow for the repetition.

Further reading

Anonymized from real automation work; adapt table and watchlist names to your environment.

Comments

Questions or corrections welcome. Sign in with GitHub to join the thread.