Skip to content

Repository files navigation

Detection Engineering — Detection-as-Code

Threat-detection rules that are measured, not just written. Every rule is scored for precision, recall and false-positive rate against labelled events, tested against real adversary telemetry, and blocked from merging if it drops below a threshold it declares for itself.

🔎 Live dashboard → — every detection, its scores and its test status. Hover any number for how it was calculated. 📖 How it works → — the full method, written for a reader with no security background as well as one with.

ATT&CK coverage

In short

  • 20 Sigma rules, each ATT&CK-mapped and scored against hand-labelled benign look-alikes: 16 Windows rules also proven to fire on a pinned public EVTX capture of the real attack, and 4 Entra ID rules on a declared, weaker evidence tier — cloud telemetry has no public captures, so those are field-validated against Microsoft's published table schema at compile time instead (ADR 0005).
  • Two independent engines check every rule — Hayabusa over real EVTX, a SQL evaluator over labelled events. They must agree; disagreement fails the build.
  • Every rule declares the score it must hit. CI fails when one drops below its own bar, and a lowered bar is rejected unless it carries a written justification.
  • Every published number is generated, then rebuilt in CI and diffed against what is committed — including the unflattering ones, and including this file.
  • Noisy signal is split, not deleted. Matching PowerShell obfuscation on its own fires on two thirds of ordinary admin scripting (measured), so that rule is informational and used for hunting; a second rule alerts at high only when obfuscation meets an execution or download sink, at an FP rate of 0.11 on the first rule's own false alarms (ADR 0006).

One command reproduces all of it from a clean checkout:

uv sync --all-extras && uv run detkit ci      # ~20s, the same sequence CI runs

Detection quality

Read FP rate first: it is the share of benign events that alerted, and unlike precision it cannot be improved by writing more attack cases. Precision moves with the malicious-to-benign ratio of the case set, which is authored here, not observed.

20 rules, 172 labelled events. Generated by detkit eval; CI fails if this table drifts from the corpus.

rule level precision recall FP rate bar it must clear
entra_audit_privileged_role_assigned high 1.00 1.00 0.00 p≥0.90, r≥1.00, fp≤0.10
proc_creation_win_impacket_wmiexec_output high 1.00 1.00 0.00 p≥0.90, r≥1.00, fp≤0.10
security_win_dcsync_replication critical 1.00 1.00 0.00 p≥0.90, r≥1.00, fp≤0.10
security_win_service_installed high 1.00 1.00 0.00 p≥0.90, r≥1.00, fp≤0.10
posh_ps_obfuscated_payload_execution high 0.83 1.00 0.11 p≥0.80, r≥1.00, fp≤0.15
proc_creation_win_encoded_powershell high 0.83 1.00 0.14 p≥0.80, r≥1.00, fp≤0.20
proc_creation_win_firewall_disabled_netsh high 0.75 1.00 0.14 p≥0.70, r≥1.00, fp≤0.20
security_win_kerberoasting_rc4_request high 0.75 1.00 0.17 p≥0.70, r≥1.00, fp≤0.25
entra_signin_legacy_auth_success medium 0.80 1.00 0.20 p≥0.80, r≥1.00, fp≤0.20
sysmon_proc_access_lsass_dump high 0.80 1.00 0.20 p≥0.75, r≥1.00, fp≤0.25
entra_audit_illicit_consent_grant high 0.80 1.00 0.25 p≥0.75, r≥1.00, fp≤0.25
security_win_asrep_roasting high 0.75 1.00 0.25 p≥0.70, r≥1.00, fp≤0.30
security_win_scheduled_task_created high 0.75 1.00 0.25 p≥0.70, r≥1.00, fp≤0.30
proc_creation_win_certutil_download_decode high 0.67 1.00 0.33 p≥0.60, r≥1.00, fp≤0.40
posh_ps_defender_tamper high 0.71 1.00 0.40 p≥0.65, r≥1.00, fp≤0.45
entra_audit_app_credential_added high 0.60 1.00 0.40 p≥0.55, r≥1.00, fp≤0.40
security_win_user_added_to_privileged_group high 0.60 1.00 0.50 p≥0.55, r≥1.00, fp≤0.55
posh_ps_susp_encoded_powershell_scriptblock informational 0.50 1.00 0.67 p≥0.45, r≥1.00, fp≤0.70
security_win_eventlog_cleared high 0.60 1.00 1.00 p≥0.55, r≥1.00, fp≤1.00
security_win_local_user_created informational 0.43 1.00 1.00 p≥0.40, r≥1.00, fp≤1.00

Called out rather than buried:

  • Two rules still alert on 100% of benign events. security_win_eventlog_cleared keeps that bar deliberately — a cleared log carries no evidence of why, so no discriminator exists, and the argument for shipping it anyway is the base rate. security_win_local_user_created cannot make that argument, so it is demoted to an audit record. Both justifications live in their case files.
  • Two rules that did have signal were fixed rather than excused. security_win_service_installed went from 0.43 precision / 1.00 FP rate to 1.00 / 0.00, and security_win_scheduled_task_created to 0.75 / 0.25, by keying on what the service or task actually executes.
  • Measurement found a real evasion. powershell.exe accepts -e as an abbreviation of -EncodedCommand; the rule tested only the longer spellings. Closing it took recall from 0.80 to 1.00 at the cost of one false positive in seven — a trade the harness refused to accept until it was written down.

Known limits

  • The benign events are authored, not captured. They prove a rule's logic discriminates; they say nothing about alert volume on a live estate.
  • The Entra rules are not proven against a real tenant. Their logic is measured and their queries are schema-validated, but nothing here replays them against captured telemetry, because none is published.
  • Sentinel coverage is partial, and it is a platform limit. Splunk gets all 16 Windows rules, source-bound; the Entra rules compile to Azure Monitor KQL bound to SigninLogs/AuditLogs. Microsoft XDR gets the process_creation subset — Sentinel's SecurityEvent table has no PreAuthType column, so the Kerberos rules cannot bind there without per-rule EventData parsing.
  • Nothing here provisions a SIEM. Rules compile to deployable, source-bound queries; running them in a real estate is out of scope. See ADR 0004 on why there is no Terraform.

Two tiers, never conflated

detections/ is my own work and carries every gate its telemetry can support. vendored/ is a pinned, attributed copy of the public SigmaHQ Windows corpus (~2,400 rules, DRL 1.1) — not my work, held to none of those gates. It runs through the same conversion pipeline at scale and shades the coverage map blue; green cells are authored. The headline number is the green one.

Running individual steps

uv run detkit validate        # metadata + fixture + ATT&CK tag discipline
uv run detkit eval            # precision / recall / FP rate per rule
uv run detkit convert         # compile to Splunk + XDR, check every query is bound
uv run detkit probe <rule>    # run a rule against its evidence (EVTX, or cases for cloud rules)
uv run detkit dashboard       # rebuild site/index.html + site/about.html
uv run pytest -v              # unit + detection tests (fetches samples, runs Hayabusa)

detkit ci installs the Hayabusa release pinned in .hayabusa-version, verified against its recorded SHA-256 — an unverified binary download is not a reproducible build. Locally, without Hayabusa, the detection tests skip so you can still work on the tooling; CI sets DETKIT_REQUIRE_HAYABUSA=1, which turns any skip into a failure. A detection suite that did not execute must never report green.

Status — complete

The corpus is closed at 20 rules and the repository is in maintenance: the gates keep running, the pinned ATT&CK release and Hayabusa version get bumped when they move, nothing new is planned. Two remaining ideas were declined in writing rather than left on an open list — more Entra rules would repeat a pattern already proven four times, and a Splunk container replaying the same pinned captures would restate gates that already pass, since what a live SIEM would really add is production volume this project does not have. ADR 0007 makes both arguments.

More

About

Detection-as-Code: Sigma rules with automated Hayabusa tests, CI, and MITRE ATT&CK coverage tracking

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages