Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Perform systematic SIEM false positive reduction through rule tuning, threshold adjustment, correlation refinement, and threat intelligence enrichment to combat alert fatigue.
.claude/skills/performing-false-positive-reduction-in-siem/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-18 | ✗→✓ | ▲ Improved | — | — |
| case-07 | ✗→✓ | ▲ Improved | — | — |
| case-13 | ✗→✓ | ▲ Improved | — | — |
| case-17 | ✗→✓ | ▲ Improved | — | — |
| case-06 | ✗→✓ | ▲ Improved | — | — |
False positive alerts are non-malicious events that trigger security rules, overwhelming SOC analysts with noise. Studies show that up to 45% of SIEM alerts are false positives, and a typical SOC analyst can only investigate 20-25 alerts per shift effectively. Reducing false positives requires systematic tuning across thresholds, correlation logic, allowlists, enrichment, and continuous validation. SIEM rules should be reviewed on a quarterly cycle at minimum.
spl# Splunk - Top 10 noisiest correlation searches index=notable | stats count by rule_name | sort -count | head 10 | eval pct=round(count / total * 100, 1)
spl# False positive rate per rule index=notable | stats count as total count(eval(status_label="Closed - False Positive")) as false_positives count(eval(status_label="Closed - True Positive")) as true_positives by rule_name | eval fp_rate=round(false_positives / total * 100, 1) | sort -fp_rate | where total > 10
spl# Before: Too sensitive - fires on 5 failed logins index=wineventlog EventCode=4625 | stats count by src_ip | where count > 5 # After: Tuned - requires 20+ failures across 3+ accounts in 10 minutes index=wineventlog EventCode=4625 | bin _time span=10m | stats count dc(TargetUserName) as unique_accounts by src_ip, _time | where count > 20 AND unique_accounts > 3
spl# Create allowlist lookup for known benign sources | inputlookup fp_allowlist.csv | fields src_ip, reason, approved_by, expiry_date # Apply allowlist in detection rule index=wineventlog EventCode=4625 | lookup fp_allowlist src_ip OUTPUT reason as allowlisted_reason | where isnull(allowlisted_reason) | stats count dc(TargetUserName) as unique_accounts by src_ip | where count > 20 AND unique_accounts > 3
spl# Before: Single-event detection (noisy) index=wineventlog EventCode=4688 New_Process_Name="*powershell.exe" | eval severity="medium" # After: Multi-signal correlation (precise) index=wineventlog EventCode=4688 New_Process_Name="*powershell.exe" | join src_ip type=left [ search index=wineventlog EventCode=4625 | stats count as failed_logins by src_ip ] | join Computer type=left [ search index=sysmon EventCode=3 | stats dc(DestinationIp) as unique_external_connections by Computer | where unique_external_connections > 10 ] | where isnotnull(failed_logins) OR unique_external_connections > 10 | eval severity=case( failed_logins > 10 AND unique_external_connections > 10, "critical", failed_logins > 5 OR unique_external_connections > 5, "high", true(), "medium" )
spl# Exclude known maintenance windows | eval hour=strftime(_time, "%H") | eval day=strftime(_time, "%A") | where NOT (hour >= "02" AND hour <= "04" AND day="Sunday") # Exclude known batch job schedules | lookup scheduled_tasks_allowlist process_name, schedule_time OUTPUT is_scheduled | where isnull(is_scheduled)
spl# Build baseline for user login patterns index=wineventlog EventCode=4624 | bin _time span=1h | stats count as logins dc(Computer) as unique_hosts by TargetUserName, _time | eventstats avg(logins) as avg_logins stdev(logins) as stdev_logins avg(unique_hosts) as avg_hosts stdev(unique_hosts) as stdev_hosts by TargetUserName | where logins > (avg_logins + 3 * stdev_logins) OR unique_hosts > (avg_hosts + 3 * stdev_hosts)
spl# Only alert when destination matches known threat intelligence index=firewall action=allowed direction=outbound | lookup ip_threat_intel_lookup ip as dest_ip OUTPUT threat_type, confidence | where isnotnull(threat_type) AND confidence > 70 # This eliminates FPs from flagging connections to benign IPs
bash# Run Atomic Red Team test after tuning to confirm detection still works # Example: Test brute force detection after threshold adjustment Invoke-AtomicTest T1110.001 -TestNumbers 1
spl# Verify detection still triggers after tuning index=notable rule_name="Brute Force Detection" earliest=-24h | stats count | where count > 0
| Metric | Formula | Target | |---|---|---| | False Positive Rate | FP / (FP + TP) 100 | < 20% | | Alert Volume Reduction | (Old Volume - New Volume) / Old Volume 100 | 30-50% per quarter | | Mean Triage Time | Total triage time / Total alerts | < 8 minutes | | Rule Precision | TP / (TP + FP) | > 0.80 | | Analyst Satisfaction | Survey score | > 4/5 |
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-03 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-18 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-07 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-13 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-17 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-21 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-10 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-20 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-06 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-09 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-11 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-14 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-01 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-23 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-04 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-12 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-08 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-19 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-16 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-05 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-02 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-15 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-22 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted. The headline lift of +43 percentage points is the difference between those two pass rates over the 23 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
The per-case answers from this run were removed by the retention sweep, so the case table below shows the verdicts without the text either arm produced. The counts above were recorded at the time and are unaffected. Answers are now kept for 180 days.
Other measured skills in the registry, with their headline benchmark lift.