GUIDES · DETECT
Backtesting a detection over 30 days.
Before a rule ever reaches an analyst, replay it across real history on the fabric. A backtest shows exactly how often the rule would have fired and on what — the fastest way to kill a false positive before it kills a SOC’s trust.
Run a backtest
Backtesting replays historical events against a detection plan without re-ingesting anything and without firing a single real action. Point it at a window and let it score.
SHELLbacktest $ td detect backtest ./detections/svc-account-from-corp-ip.yml --since 30d -> replaying 1.84B events from the fabric... -> 14 matches across 30 days -> 3 in allowlisted windows . 11 candidate true positives ok report at /detections/_preview/backtest/bt_9Hk2
Read the results
The report is not just a count. It clusters matches so you can tell a real pattern from a noisy one, and flags the matches that overlap known-good activity as false-positive candidates to suppress.
| Signal | Read it as |
|---|---|
| Match rate | Roughly how many cases per day this rule will open. Over ~5/day for a HIGH, retune. |
| FP candidates | Matches overlapping allowlisted windows or service accounts — add an except. |
| Clusters | Many matches on one entity is usually one incident, not many — good. Spread across unrelated entities on a narrow rule is suspicious. |
Gate CI on the number
Make the backtest a merge gate. A pull request that would introduce false positives fails before it can ship.
YAML.github/workflows/detections.yml - run: td detect backtest ./detections --since 30d --max-fp 0
Backtests are free. Because compute is decoupled from storage, a replay reads the past without re-ingesting it and without side effects — run one on every change, not just the scary ones.
Where to go next
- Detection-as-code — the full author-test-ship loop.
- Data fabric — why replay over history is cheap.