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:
| Trigger | Source of the IP |
|---|---|
| New Sentinel incident / alert | The IP entity on a triaged malicious incident |
| Scheduled recurrence | A 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
- Microsoft Sentinel watchlists — Microsoft Learncreating and querying watchlists
- Automate responses with playbooksLogic Apps / Power Automate flows for Sentinel
- Investigating a phishing report with KQLwhere these IOCs come from in the first place
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.