Security Notes
Detection & Vulnerability Management

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).

21 min read 12 sections 8 model answers verified 2026-10

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

Ignored alerts are blind spots with a dashboard.

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.

Bad tuning is worse than no tuning.

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.

Interviewers use this to separate operators from engineers.

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.

True positive (TP)

The rule fired, the behaviour happened, and it was malicious or unauthorised. Escalate.

Benign true positive (BTP)

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.

False positive (FP)

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.

False negative (FN)

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.

  1. 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
  2. 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)
  3. 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
  4. 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
  5. 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?

Process name only

Weakest. Attacker renames their binary to CcmExec.exe. Never use alone.

Command-line substring

Weak. Attacker adds the magic string as an argument or comment: powershell -c "# ccm" ....

Full image path

Medium. Protected system paths need admin rights to write; user-writable paths (%TEMP%, C:\Users\Public) are worthless.

Parent–child lineage

Strong. Pins who launched it: services.exe β†’ CcmExec.exe β†’ powershell.exe. Harder to fake without the real parent.

Hash or code-signing signer

Strong for the binary, but breaks on every update (hash) and trusts everything the vendor signs (signer) β€” see 3CX.

User + host group + time window

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:

yaml
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_sccm

And the matching entry in the exception register (the list of every approved exclusion, kept in Git next to the rules):

yaml
- 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:

Pick the entity and the measure.

Per user: bytes uploaded per day. Per service account: distinct APIs called per hour. Per host: outbound connections to new domains.

Build a rolling window.

Compare today against the previous 30 days for the same entity, not against everyone. A database admin's normal is a salesperson's anomaly.

Measure spread, not just average.

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.

Respect seasonality.

Compare Mondays with Mondays and business hours with business hours, or month-end finance jobs will alert every month.

Add an absolute floor.

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Γ— normal

current=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 MailItemsAccessed audit 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.

Warning

Baselines 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.

  1. Encoded PowerShell from Wordattack09:02
    • +10 risk to host FIN-WKS-03 (tactic: execution) β€” no alert
  2. New scheduled task createdattack09:05
    • +15 risk (tactic: persistence) β€” total 25, no alert
  3. Process opens lsass.exe memoryattack09:11
    • +40 risk (tactic: credential access) β€” total 75
  4. 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.

Enrich automatically.

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.

Pre-filter with audit.

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.

Route by playbook.

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.

Close with structured data.

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 precursor

Triage and Detection Quality Metrics

Last verified2026-10

Metrics should answer two questions: is each rule worth its cost, and is the analyst workload sustainable?

Precision

Of the alerts a rule produced, how many were true positives. TP Γ· (TP + FP + BTP). The single best per-rule health number.

False positive rate (FPR)

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.

MTTD β€” Mean Time to Detect

From the attacker's first action to the first alert. Measured from incidents and purple-team tests, not from alerts.

MTTT / MTTA β€” Mean Time to Triage / Acknowledge

From alert created to an analyst's first decision. Rises first when a queue is overloaded.

MTTR β€” Mean Time to Respond

From alert to containment. Meaningless if averaged across FPs closed in 30 seconds.

Rule-level ROI

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.

Alerts per analyst per shift

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:

Alerts closed per day

rewards fast closing, including closing real alerts without looking.

Number of rules deployed

or % of ATT&CK techniques "covered" β€” counts rules, not whether they fire or are trusted. Ten noisy rules on one technique count as "covered."

Average MTTR across all alerts

dominated by quick FP closures; a falling MTTR can mean analysts gave up.

Zero incidents this quarter

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.

Detection-as-Code (DaC)

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.

Exception register

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.

Palantir ADS (Alerting and Detection Strategy) framework

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:

Borrowed exclusions.

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.

Hiding in the BTP pile.

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).

Baseline poisoning and slow ramps.

Covered above: long fixed reference windows and trend alerts.

Silent coverage loss.

Disabling a rule "temporarily" during a flood and never turning it back on. Track rule state changes as auditable events.

Automation as an exclusion.

SOAR auto-close rules bypass review. Put them in the register and sample them.

Political exclusions.

"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

Q
How do you distinguish a false positive from a benign true positive, and why must they be tuned differently?
Model answer

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.

Q
A high-volume rule floods the SOC queue. What is your step-by-step method?
Model answer

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.

Q
How do you exclude legitimate admin tooling like PowerShell, PsExec or CI/CD runners without creating a blind spot?
Model answer

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.

Q
How do you apply statistical and behavioural baselining to catch credential abuse or off-hours data transfers?
Model answer

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.

Q
Which SOC metrics best show detection health and analyst workload, and which are vanity metrics?
Model answer

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.

Q
How do you structure the feedback loop between Tier 1 analysts and detection engineers?
Model answer

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.

Q
Someone senior asks for a broad exclusion on an executive's laptop or on the domain controllers. How do you handle it?
Model answer

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.

Q
Why use correlation or risk-based alerting instead of tuning every weak signal individually?
Model answer

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.