<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: wislest</title>
    <description>The latest articles on DEV Community by wislest (@wislest).</description>
    <link>https://dev.to/wislest</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4020788%2F49a1998b-b2ad-4b02-a400-d2dd9e079003.png</url>
      <title>DEV Community: wislest</title>
      <link>https://dev.to/wislest</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/wislest"/>
    <language>en</language>
    <item>
      <title>Backtesting detection rules — including the ones for AI attacks</title>
      <dc:creator>wislest</dc:creator>
      <pubDate>Thu, 09 Jul 2026 13:00:00 +0000</pubDate>
      <link>https://dev.to/wislest/backtesting-detection-rules-including-the-ones-for-ai-attacks-4k80</link>
      <guid>https://dev.to/wislest/backtesting-detection-rules-including-the-ones-for-ai-attacks-4k80</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://wistonlestin.com/posts/backtesting-detection-rules" rel="noopener noreferrer"&gt;my blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;How a lottery-prediction backtester became a harness for measuring detection rules, and two things it revealed on real public data.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The premise
&lt;/h2&gt;

&lt;p&gt;Detection engineers write rules — Sigma, KQL analytics, the newer Agent Threat Rules for AI agents — but the feedback loop is weak. You ship a rule and find out how good it was from production noise, weeks later. The question you actually want answered before shipping is simple: &lt;strong&gt;against my historical data, how many real attacks does this rule catch, and how much noise does it make?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That is a backtest. And the machinery for it is the same machinery you'd build to backtest a market strategy, or — in my case — a lottery predictor.&lt;/p&gt;

&lt;p&gt;Yes, I once built a system to predict lottery numbers. No, it doesn't work, and I know that precisely because I made the harness prove it: every model run against a pure-random baseline over ten thousand simulations, with a chi-square test to catch any edge over chance. None of them had one. That could have been the end of the project. Instead it was the useful part: the harness around those useless models was solid. Replay history, score each predictor, rank them, weight an ensemble, track drift, all of it spent on a coin flip.&lt;/p&gt;

&lt;p&gt;So I lifted the harness out, threw away the models, and rewrote the scoring core as a confusion matrix. The result is the &lt;a href="https://github.com/wislest/detection-rule-backtester" rel="noopener noreferrer"&gt;Detection Rule Backtester&lt;/a&gt;. A rule becomes a binary classifier over events:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;predicted positive&lt;/strong&gt; := the rule fired on the event&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;actual positive&lt;/strong&gt; := the event is part of an attack (ground-truth label)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From there you get precision, recall, F1 and false-positive rate per rule, rankings, and a multi-rule ensemble confidence. Two tracks share one harness: classic logs (Sysmon/EVTX) and AI-attack traces (LLM prompts, agent tool calls).&lt;/p&gt;

&lt;p&gt;Below are two findings from running it on real public data — one from each track — because a harness is only interesting if it tells you something you didn't already know.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 1 (classic logs): a rule can be right and still blind
&lt;/h2&gt;

&lt;p&gt;I ran two Sigma rules against a real OTRF Security-Datasets capture — an Empire launcher that uses regsvr32 to pull a remote &lt;code&gt;.sct&lt;/code&gt; scriptlet (Squiblydoo, T1218.010) which spawns an encoded-PowerShell agent (T1059.001) beaconing to a C2. 2,390 events.&lt;/p&gt;

&lt;p&gt;Since these captures don't ship per-event labels, I anchored ground truth on the attack's IOCs — the C2 address and the regsvr32 → powershell process lineage — deliberately independent of the process patterns the rules test, to avoid grading the rules on their own answer key.&lt;/p&gt;

&lt;p&gt;Both rules fired at precision 1.0, which looked fine until I checked the Squiblydoo rule's recall: &lt;strong&gt;0.5&lt;/strong&gt;. It had caught the attack once and missed it once, and that stopped me for a minute, because there was only one attack to catch.&lt;/p&gt;

&lt;p&gt;The answer was that the same process creation gets logged twice — once by Sysmon (EventID 1) and once by Windows Security (EventID 4688). The rule keys on &lt;code&gt;Image&lt;/code&gt;, a Sysmon field name; the 4688 record carries the identical data under &lt;code&gt;NewProcessName&lt;/code&gt;. Same event, different schema, and a rule written against one schema is blind to the other.&lt;/p&gt;

&lt;p&gt;The fix is not cleverer regex — it's &lt;strong&gt;field normalization&lt;/strong&gt;, the same idea as Sigma processing pipelines or the OSSEM data dictionary. Map the Security field names onto the Sysmon names before evaluating:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;events&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;load_sysmon_events&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;capture&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;normalize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 4688 gains Image/ParentImage
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Scoped recall goes from 0.5 to 1.0. The harness printed both runs side by side, and that is what I wanted from it: not just a score, but a hint at where the rule was losing ground, and a way to confirm the fix actually moved the number.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 2 (AI attacks): keyword rules barely dent real jailbreaks
&lt;/h2&gt;

&lt;p&gt;For AI attacks I replayed 666 in-the-wild jailbreak prompts shipped with NVIDIA's garak, plus a few named DAN / Developer-Mode payloads, mixed with benign prompts so precision and false-positive rate mean something.&lt;/p&gt;

&lt;p&gt;A classic prompt-injection rule keyed on instruction-override phrasing — "ignore previous instructions", "developer mode", "reveal your system prompt" — caught &lt;strong&gt;8.8% (59/669)&lt;/strong&gt;, at precision 1.0.&lt;/p&gt;

&lt;p&gt;The low number is the interesting part. When I counted markers across the corpus, literal override phrasing turned out to be rare: "ignore/disregard previous" appears in 6.5% of prompts, "developer mode" in 3.6%. Real jailbreaks are dominated by &lt;strong&gt;role-play framing&lt;/strong&gt; — "you are now", "act as", "pretend to be" — at 42.6%, and &lt;strong&gt;restriction-removal framing&lt;/strong&gt; — "no restrictions", "unfiltered" — at 33.2%.&lt;/p&gt;

&lt;p&gt;So I added three more rule families for exactly those patterns and let the harness rank them and vote. Ensemble recall rose to &lt;strong&gt;70.4% (471/669)&lt;/strong&gt;, still with zero false alarms on the benign set. The ranking the harness produced:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Rank&lt;/th&gt;
&lt;th&gt;Rule family&lt;/th&gt;
&lt;th&gt;Ensemble weight&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Role-play / persona framing&lt;/td&gt;
&lt;td&gt;0.40&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Restriction-removal framing&lt;/td&gt;
&lt;td&gt;0.30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;DAN persona&lt;/td&gt;
&lt;td&gt;0.20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Instruction-override keywords&lt;/td&gt;
&lt;td&gt;0.10&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The override-keyword rule, the one most people reach for first, ranks last. And even all four families together leave about 30% of real jailbreaks uncaught. That residual is the honest argument for semantic detection: keyword and regex rules are a cheap first layer with a hard ceiling, and now I can point to where the ceiling sits instead of just asserting there is one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this shape matters
&lt;/h2&gt;

&lt;p&gt;Neither finding is exotic. What's useful is that both came out of the same measurement loop, and the loop is honest about its own limits: the Sigma evaluator ignores logsource (so pre-filter by source), scoped recall depends on rule and corpus taxonomies agreeing, and IOC-based labels are approximate. Those caveats are in the README, not hidden, because a backtester that flatters your rules is worse than none.&lt;/p&gt;

&lt;p&gt;The through-line from a dead lottery project turned out to be simple: the value was never in the models, it was in the harness that measured them. Repointed at detection rules, classic and AI-facing alike, it finally has something worth measuring.&lt;/p&gt;

&lt;p&gt;Code, rules and reproducible runs: &lt;a href="https://github.com/wislest/detection-rule-backtester" rel="noopener noreferrer"&gt;github.com/wislest/detection-rule-backtester&lt;/a&gt;. Every number above regenerates from &lt;code&gt;examples/run_real_dataset.py&lt;/code&gt; and &lt;code&gt;examples/run_real_garak.py&lt;/code&gt; after &lt;code&gt;bash scripts/fetch_datasets.sh&lt;/code&gt;.&lt;/p&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>llm</category>
      <category>cybersecurity</category>
    </item>
  </channel>
</rss>
