SIEM Tuning, Baselining & Triage Metrics
Most alert queues are not drowning because the attackers are busy. They are drowning because rules were shipped and never maintained. This page treats alert fatigue as an engineering problem: how to classify noise, tune a rule without blinding yourself, baseline "normal" with statistics, merge weak signals into strong incidents, and measure whether any of it worked. It builds on detection engineering (how rules are written) and the SIEM pipeline (how data reaches them).
Last verified2026-10
Memory hook"fix the logic, scope the context, prove it still fires." A false positive means the rule logic is wrong: fix the logic. A benign true positive means the rule is right but the activity is approved: scope an exception to exactly that context. Every tuning change ships with a test proving the malicious case still alerts.
What Is This?
A SIEM (Security Information and Event Management) rule says "alert me when X happens." Tuning means adjusting that rule after it meets real data, so it fires on what you meant and stays quiet on everything else. Baselining means learning what normal looks like for a specific user, host, or service, so you can alert when they behave unusually rather than when they match a fixed pattern. Triage metrics are the numbers that tell you whether your rules and your analysts are healthy: how many alerts were real, how long they waited, how much analyst time each rule costs.
Why It Matters
When a rule fires 300 times a day and is always wrong, analysts start bulk-closing it. The one real alert in that rule is then closed too.
A broad exclusion ("ignore anything from C:\Program Files\") removes noise and coverage at the same time, and nobody notices the coverage loss until an incident review.
A weak candidate says "we tuned out the noise." A strong one says which field they scoped on, why that field can't be spoofed, how they proved the rule still fires, and when the exception expires.
The Taxonomy of Alert Noise
Every closed alert should get one of four labels. The labels decide what kind of fix is needed, so getting them right is the first tuning step.
The rule fired, the behaviour happened, and it was malicious or unauthorised. Escalate.
The rule fired, the behaviour really happened, but it was authorised: IT ran PsExec, the red team dumped LSASS, a backup job read every file. The rule is correct.
The rule fired but the behaviour it targets did not happen: a regex for net user /add matched a help-desk ticket body, or a parser put the wrong value in a field. The rule is wrong.
The behaviour happened and the rule did not fire. Invisible until a hunt, a purple-team test, or an incident finds it.
Why the split mattersa false positive is a bug in the detection, so you fix the logic, and the fix applies everywhere. A benign true positive is a correct detection of approved activity, so you add a narrow exception for that approved context only. If you "fix" a BTP by changing the logic (for example, removing PsExec from the rule), you have deleted the detection for every attacker who uses PsExec too.
In practiceDisposition codes an analyst picks at close (the exact names vary by platform; Microsoft Sentinel uses True Positive, Benign Positive, False Positive, Undetermined):
ALERT-88412 rule=win_psexec_remote_service host=FIN-WKS-022
disposition: BENIGN_TRUE_POSITIVE β behaviour real, approved
reason: CHG-20931 patch deployment by svc-patching from JMP-01
tuning_req: scope exception to (user=svc-patching, src=JMP-01, window=Tue 22:00-02:00)
ALERT-88419 rule=win_net_user_add host=HELPDESK-07
disposition: FALSE_POSITIVE β behaviour did not happen
reason: "net user /add" string inside Outlook.exe command line (KB article link)
tuning_req: require Image endswith \net.exe or \net1.exeπ―On the job. When you review a queue, sort closures by rule and look at the free-text reasons. "Closed, known activity" with no ticket or owner is the smell: it means an analyst decided something was benign without evidence. Make the reason field mandatory and require a change ticket or asset-owner confirmation for any BTP.
The Systematic Tuning Lifecycle
When one rule floods the queue, follow the same five steps every time. Skipping step 2 is how broad exclusions happen.
-
Contain the flooddefendfirst hour
- Do not disable the rule. Lower its severity or route it to a separate low-priority queue so the main queue recovers
- Note the time you did it: everything after this is a known coverage change
-
Root-cause the noisedefendday 1
- Pull 7β30 days of hits and group by every useful field: user, host, parent process, command line, hash, source IP
- Usually one or two values account for 80%+ of volume
- Label each group: FP (logic bug) or BTP (approved activity)
-
Modifydefendday 1β2
- FP β fix the logic (tighter field, correct parser, require a second condition)
- BTP β narrow exception scoped to the full context, with an owner and expiry date
-
Test and peer reviewdefendbefore merge
- Replay a known-malicious sample: the rule must still fire
- Replay the last 30 days: the volume must drop by the amount you expected
- A second engineer reviews the exception for spoofable fields
-
Deploy and watchdefend1β2 weeks
- Restore severity, compare volume and precision before vs after
- Set a reminder for the exception expiry date
In practiceStep 2 in Splunk SPL (Search Processing Language): group a noisy rule's hits to find the dominant source.
index=notable search_name="win_encoded_powershell" earliest=-14d
| stats count by user, dest, parent_process, process_hash
| sort - count
| eventstats sum(count) as total
| eval pct=round(count/total*100,1)
user dest parent_process count pct
SYSTEM WKS-* C:\Windows\CCM\CcmExec.exe 4112 86.3 β SCCM client: approved, so BTP
jdoe FIN-WKS-03 C:\Program Files\...\EXCEL.EXE 3 0.1 β Excel spawning encoded PowerShell: investigate
...The 0.1% row is the reason the rule exists. Any tuning change must leave that row alerting.
π°Real incident β 3CX supply chain (2023, CVE-2023-29059). A trojanised version of 3CX's signed desktop phone app was pushed to customers through the vendor's normal update channel. From around 22 March 2023, endpoint products started flagging it, and users on the vendor's community forum spent about a week discussing the alerts, with many assuming a false positive and allow-listing the app. It was a North Koreaβattributed supply-chain compromise. The lesson: "it's a signed vendor binary" is a reason to investigate, not a disposition. Label it BTP only after someone proves the behaviour is the expected one.
Precision-Scoped Exclusions
An exclusion is a statement: "this exact activity, by this actor, in this place, is approved." The more fields it pins, the less an attacker can borrow it. The test for every field is: can an attacker who already has code execution on the host control this value?
Weakest. Attacker renames their binary to CcmExec.exe. Never use alone.
Weak. Attacker adds the magic string as an argument or comment: powershell -c "# ccm" ....
Medium. Protected system paths need admin rights to write; user-writable paths (%TEMP%, C:\Users\Public) are worthless.
Strong. Pins who launched it: services.exe β CcmExec.exe β powershell.exe. Harder to fake without the real parent.
Strong for the binary, but breaks on every update (hash) and trusts everything the vendor signs (signer) β see 3CX.
Strong context. The service account, only from the jump host, only in the change window.
Combine at least two strong fields. Never exclude a whole host, a whole user, or a whole directory.
In practiceA Sigma rule (vendor-neutral detection format) for encoded PowerShell, with a weak and a strong exclusion side by side:
title: Encoded PowerShell command line
detection:
selection:
Image|endswith: '\powershell.exe'
CommandLine|contains: ' -enc'
# BAD: attacker just names a parent CcmExec.exe in %TEMP%
# filter_sccm:
# ParentImage|endswith: 'CcmExec.exe'
filter_sccm:
ParentImage: 'C:\Windows\CCM\CcmExec.exe' # exact protected path, not a suffix
User|contains: 'SYSTEM'
ComputerName|startswith: 'WKS-' # workstations only; servers stay covered
condition: selection and not filter_sccmAnd the matching entry in the exception register (the list of every approved exclusion, kept in Git next to the rules):
- id: EXC-0142
rule: win_encoded_powershell
scope: parent=C:\Windows\CCM\CcmExec.exe, user=SYSTEM, host=WKS-*
reason: SCCM compliance baselines run encoded scripts (CHG-20211)
owner: endpoint-engineering@corp β someone to ask when it looks wrong
approved_by: detection-lead
expires: 2027-01-31 β forces a re-review, not permanent trustπ°Real incident β Kaseya VSA / REvil (2021, CVE-2021-30116). REvil exploited the Kaseya VSA remote-management server and pushed a fake "hotfix" to managed endpoints. The payload was dropped into the agent's working folder,
C:\kworking, which Kaseya's own guidance told customers to exclude from antivirus scanning. Roughly 60 managed service providers and up to 1,500 downstream businesses were hit. The lesson: a directory exclusion is an invitation, and attackers read vendor documentation too.
π―On the job. Admin tools are the hardest case because attackers use the same tools (PsExec, PowerShell, WMI, CI/CD runners). Scope by who and from where, not by what: PsExec is approved only for the patching service account, only from the two jump hosts, only to workstation targets. A CI/CD runner exclusion pins the runner's service account and the pipeline's job identity, and still alerts when the runner reaches out to a host or domain it has never contacted.
Statistical and Behavioral Baselining
Fixed rules catch known patterns ("PsExec ran"). Baselines catch unusual for this entity ("this user uploaded 40 times more than their normal"). The steps:
Per user: bytes uploaded per day. Per service account: distinct APIs called per hour. Per host: outbound connections to new domains.
Compare today against the previous 30 days for the same entity, not against everyone. A database admin's normal is a salesperson's anomaly.
Standard deviation (Ο) says how much the value normally wobbles. A z-score is "how many Ο away from the average is today": z = (today β average) Γ· Ο. A common starting threshold is z > 3.
Compare Mondays with Mondays and business hours with business hours, or month-end finance jobs will alert every month.
A user going from 1 MB to 10 MB is z = 50 and completely harmless. Require both "unusual" and "big enough to matter."
In practiceOff-hours exfiltration baseline in SPL:
index=proxy action=allowed
| bin _time span=1d
| stats sum(bytes_out) as daily_out by user, _time
| streamstats window=30 current=f avg(daily_out) as avg_out stdev(daily_out) as sd_out by user
| eval z=round((daily_out-avg_out)/sd_out,1)
| where z>3 AND daily_out>500000000
user _time daily_out avg_out z
mkowal 2026-10-11 6.2 GB 140 MB 38.4 β Saturday upload, 44Γ normalcurrent=f keeps today out of its own baseline; otherwise the spike inflates the average and Ο it is compared against.
Where the maths breaksUpload volumes are skewed: most days are small, a few are huge. Standard deviation gets dragged around by the huge days, so teams often switch to the median and the median absolute deviation (MAD), which ignore outliers when building the baseline. New users have no history, so they need a peer-group baseline (same team, same role) until they have 30 days of their own.
UEBA (User and Entity Behaviour Analytics) is this idea productised: the platform keeps baselines per user and device across many measures and outputs an anomaly score. Treat the score as a triage input that raises priority, not as a standalone page. The identity-focused version is in identity threat detection.
π°Real incident β Storm-0558 (2023). A China-based actor forged Microsoft authentication tokens and read the email of US government officials. The US State Department spotted it with a custom alert on
MailItemsAccessedaudit events: mail was being read by an application ID that did not normally access their mailboxes. Microsoft's own detections had not flagged it. The lesson: a baseline of "which apps normally touch this data" caught a stolen-key attack that no signature could.
WarningBaselines can be poisonedAn attacker who ramps exfiltration slowly (10% more each week) teaches the baseline that the new level is normal. Defences: keep a long, fixed reference window alongside the rolling one, alert on the trend of the baseline itself, and never let one entity's baseline grow without limit.
Correlation and Multi-Signal Detections
Many useful signals are too weak to page on alone: an encoded PowerShell command, a new scheduled task, a first-time login from a new country. Each one alone is mostly benign. Three of them on the same host within an hour almost never are.
Risk-based alerting turns weak signals into risk points attached to an entity (user or host). Nothing pages until the entity's total crosses a threshold, or until signals from several different ATT&CK tactics (attacker goals such as execution, persistence, credential access) stack up.
-
Encoded PowerShell from Wordattack09:02
- +10 risk to host FIN-WKS-03 (tactic: execution) β no alert
-
New scheduled task createdattack09:05
- +15 risk (tactic: persistence) β total 25, no alert
-
Process opens lsass.exe memoryattack09:11
- +40 risk (tactic: credential access) β total 75
-
Threshold crossed: 3 tactics, risk β₯ 70 in 24 hdefend09:11
- One incident created with all three signals attached; analyst sees the story, not three tickets
In practiceThe incident an analyst receives:
INCIDENT-5521 entity=host:FIN-WKS-03 risk=75 tactics=3 β one page instead of three noisy alerts
09:02 T1059.001 powershell.exe -enc JABz... parent=WINWORD.EXE
09:05 T1053.005 schtasks /create /tn "OneDriveUpdate" ...
09:11 T1003.001 rundll32.exe β lsass.exe GrantedAccess=0x1010π―On the job. When a rule is noisy but the behaviour matters, demote it from "alert" to "risk contributor" instead of deleting it. Check that the correlation window is long enough: a patient attacker spreading steps over three days will slip under a one-hour window.
Operational Alert Triage Workflows
Triage is the first look at an alert: is it real, how bad, who handles it. Most of it can be prepared before an analyst opens the ticket.
Attach asset owner and criticality (is this a domain controller or a test VM?), the user's role and manager, whether the IP or hash has been seen before, and every other alert on this entity in the last 7 days.
SOAR (Security Orchestration, Automation and Response) playbooks can auto-close alerts that match a documented BTP pattern. Every auto-close records which exception matched, and a random sample (for example 5%) is reviewed by a human each week. An auto-close rule is an exclusion and belongs in the exception register.
Identity alerts go to the identity queue with the identity playbook; cloud alerts go to the cloud queue. Severity comes from rule severity Γ asset criticality, so the same alert on a domain controller outranks it on a kiosk.
Disposition code, reason, linked ticket. This is the raw material for every metric below.
In practiceWhat enrichment adds to a raw alert:
raw: user=a.nowak src_ip=185.220.101.4 action=login_success app=VPN
enriched: user_role=Finance Controller asset_criticality=high
ip_tag=Tor exit node β changes priority instantly
user_usual_countries=[PL] this_login_country=DE
prior_alerts_7d=1 (MFA push denied Γ4, 08:51) β MFA fatigue precursorTriage and Detection Quality Metrics
Last verified2026-10
Metrics should answer two questions: is each rule worth its cost, and is the analyst workload sustainable?
Of the alerts a rule produced, how many were true positives. TP Γ· (TP + FP + BTP). The single best per-rule health number.
Strictly FP Γ· (FP + true negatives), but in a SOC true negatives are unknown, so teams usually mean "share of alerts closed as FP". Say which one you mean.
From the attacker's first action to the first alert. Measured from incidents and purple-team tests, not from alerts.
From alert created to an analyst's first decision. Rises first when a queue is overloaded.
From alert to containment. Meaningless if averaged across FPs closed in 30 seconds.
Analyst minutes spent on a rule Γ· true positives it produced. "This rule costs 41 hours per real finding" is a sentence that gets a rule fixed.
Sustainable load. If it is over what a person can investigate properly, quality is already falling.
Worked exampleA rule fires 600 times in 30 days: 3 TP, 420 BTP, 177 FP. Precision = 3 Γ· 600 = 0.5%. At 6 minutes per alert that is 60 analyst hours for 3 findings: 20 hours per true positive. Scoping the BTP exception removes 420 alerts; fixing the parser bug removes 177. The rule now fires 3 times, all real, and precision is 100%.
Vanity metrics look good on a slide and tell you nothing, or reward the wrong behaviour:
rewards fast closing, including closing real alerts without looking.
or % of ATT&CK techniques "covered" β counts rules, not whether they fire or are trusted. Ten noisy rules on one technique count as "covered."
dominated by quick FP closures; a falling MTTR can mean analysts gave up.
equally consistent with good security and blind detection.
π―On the job. Report precision and ROI per rule, with the top ten worst rules named, every week. Pair MTTT with a quality check (a sample of closed alerts re-reviewed by a senior analyst), so speed can't be bought by sloppiness. Measure MTTD only from real incidents and purple-team exercises, where you know when the attacker started.
Governance and Feedback Loops
Tuning decisions are security decisions, so they get the same controls as code changes.
rules, exceptions and test samples live in Git. Every tuning change is a pull request with a reviewer, a malicious-sample test that must still fire, and a link to the analyst feedback that triggered it.
one file listing every exclusion with scope, reason, owner, approver and expiry. A CI check fails the build when an exception has expired, which forces a re-review.
a documentation template published by Palantir in 2017. Every rule gets a write-up covering: Goal, Categorization (ATT&CK mapping), Strategy Abstract, Technical Context, Blind Spots and Assumptions, False Positives, Validation, Priority, Response, and Additional Resources. The Blind Spots and False Positives sections are where tuning decisions are recorded, so the next engineer knows what was deliberately excluded.
The loop between Tier 1 analysts and detection engineers:
sequenceDiagram
participant T1 as Tier 1 analyst
participant Q as Case system
participant DE as Detection engineer
participant Git as Rules repo (CI)
T1->>Q: Close alert<br/>disposition + reason + ticket
Q->>DE: Weekly report<br/>worst precision / ROI rules
DE->>Git: PR: fix logic or scoped exception<br/>+ malicious test sample
Git->>Git: CI replays samples<br/>blocks if TP stops firing
Git-->>T1: Release note<br/>what changed, what to watchπ―On the job. Give analysts a one-click "tuning request" on every alert that pre-fills rule, entity and suggested scope. If the only way to give feedback is a chat message, feedback stops and analysts quietly build their own mental exclusions instead.
Security Angle
What attackers exploit and what to watch for:
Attackers look for what defenders ignore: the RMM (remote monitoring and management) agent's folder, the backup service account, the CI runner. Kaseya's C:\kworking and SolarWinds' own guidance to exclude Orion folders from scanning are the classic examples. Audit every exclusion for fields the attacker can set.
An attacker using PsExec during a patch window, from a compromised jump host, looks exactly like the approved activity. Exceptions must still alert on anything outside the approved shape (new target type, new command line, outside the window).
Covered above: long fixed reference windows and trend alerts.
Disabling a rule "temporarily" during a flood and never turning it back on. Track rule state changes as auditable events.
SOAR auto-close rules bypass review. Put them in the register and sample them.
"Exclude the CEO's laptop, the alerts are embarrassing" or "exclude the domain controllers, they're too noisy." These remove coverage from the highest-value targets. Answer with a scoped alternative plus a signed risk acceptance, never a silent broad exclusion.
Interview Questions
A false positive means the behaviour the rule targets didn't happen, for example a "net user /add" regex matching text inside an email, so the rule logic is wrong and I fix the logic. A benign true positive means the behaviour really happened but was approved, like IT running PsExec for patching, so the rule is right and I add a narrow exception for that exact actor, host and window. Mixing them up is dangerous: if I "fix" a BTP by removing PsExec from the rule, I've deleted the detection for every attacker who uses PsExec too.
First I contain the flood without disabling the rule, by dropping its severity or routing it to a side queue, and I note the time because that's a coverage change. Then I pull two to four weeks of hits and group them by user, host, parent process and hash; usually one or two values are 80% of the volume, and I label each group as a logic bug or approved activity. I fix the logic for FPs, write scoped, expiring exceptions for BTPs, and before merge I replay a known-malicious sample to prove it still fires and replay the last 30 days to confirm the volume drop, with a peer review of the exception.
I scope by who and from where rather than by tool name, because attackers use the same tools. For each field I ask whether an attacker with code execution could set it: process names and command-line substrings fail that test, while an exact protected path, parent-child lineage, a specific service account and a specific jump host pass it. So PsExec is excepted only for svc-patching from the two jump hosts to workstations in the change window, with an owner and expiry in the exception register; Kaseya's C:\kworking exclusion in 2021 is the example of what happens when you exclude a whole folder.
I baseline per entity, not globally: each user's daily upload volume over a rolling 30-day window that excludes today, then compute a z-score and alert above about three standard deviations, but only when it's also over an absolute floor like 500 MB so tiny users don't alert on trivial jumps. I compare like with like for seasonality, use median and MAD when the data is skewed, and use peer groups for new users. The State Department caught Storm-0558 in 2023 with exactly this idea, an app ID reading mailboxes it normally never touched, and the catch is poisoning: a slow ramp teaches the baseline, so I keep a long fixed reference window too.
Per rule, precision and cost per true positive are the most honest: a rule that fires 600 times for 3 real findings at six minutes each costs 20 analyst hours per finding, which makes the case for fixing it. For workload I watch time to triage and alerts per analyst per shift, paired with a sample re-review so speed doesn't come from sloppiness, and MTTD measured from real incidents and purple-team tests. Vanity metrics are alerts closed per day, number of rules, raw ATT&CK coverage counts and average MTTR across all alerts, because each one can improve while detection gets worse.
Analysts close every alert with a structured disposition, reason and ticket, and have a one-click tuning request; that data feeds a weekly report of the worst-precision rules to detection engineering. Engineers make the change as a pull request in a detection-as-code repo, with a malicious test sample that CI replays so the rule can't silently stop firing, and the exclusion recorded in an exception register with an expiry. Each rule's Palantir ADS write-up records the Blind Spots and False Positives sections, so the reasoning behind a tuning decision outlives the person who made it.
I don't say a flat no, and I don't silently comply: I find out which specific alerts are the problem and offer a scoped fix for those, because executives and domain controllers are the targets attackers want most, so they are the last places to go blind. If they still want the broad exclusion, it becomes a documented risk acceptance signed by the asset owner and the CISO, with an expiry date and a compensating control, such as routing those alerts to a small dedicated queue instead of suppressing them. That turns a political request into a visible, owned decision rather than a hidden gap.
Some behaviours are too common to page on alone, like encoded PowerShell or a new scheduled task, but three of them on one host within an hour, spanning execution, persistence and credential access, almost never are. Risk-based alerting gives each weak signal points on the entity and only creates an incident when the total or the number of distinct ATT&CK tactics crosses a threshold, so analysts get one story instead of three noisy tickets. The catch is the time window: a patient attacker spreading steps over days slips under a one-hour window, so I keep a longer rolling window as well.