<?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: Oracle Mode</title>
    <description>The latest articles on DEV Community by Oracle Mode (@janissary).</description>
    <link>https://dev.to/janissary</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%2F4181472%2Ffaeb7b16-63a3-44a6-9417-35605cda98d2.png</url>
      <title>DEV Community: Oracle Mode</title>
      <link>https://dev.to/janissary</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/janissary"/>
    <language>en</language>
    <item>
      <title>JANISSARY 7.3.0: F1 0.921, reproducible in one command</title>
      <dc:creator>Oracle Mode</dc:creator>
      <pubDate>Sun, 25 Oct 2026 01:55:26 +0000</pubDate>
      <link>https://dev.to/janissary/janissary-730-f1-0921-reproducible-in-one-command-1fp1</link>
      <guid>https://dev.to/janissary/janissary-730-f1-0921-reproducible-in-one-command-1fp1</guid>
      <description>&lt;h1&gt;
  
  
  JANISSARY 7.3.0: F1 0.921, reproducible in one command
&lt;/h1&gt;

&lt;p&gt;7.3.0 is out. Code freeze — functionally complete, frozen to prevent regressions. Release notes: &lt;a href="https://github.com/Hetaireia/janissary/releases/tag/7.3.0" rel="noopener noreferrer"&gt;github.com/Hetaireia/janissary/releases/tag/7.3.0&lt;/a&gt;. Docker: &lt;code&gt;oraclemode337/janissary:7.3.0&lt;/code&gt;. PyPI: &lt;code&gt;janissary==7.3.0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; F1 0.921 on the OWASP Benchmark DAST-applicable corpus. Precision 1.000. Committed run artifacts. One command to reproduce. And here's what it misses, before you install.&lt;/p&gt;

&lt;h2&gt;
  
  
  The benchmark number — reproduced, not asserted
&lt;/h2&gt;

&lt;p&gt;The 0.920 figure from the earlier post was measured on v7.2.0. The v7.2.0 run artifacts weren't committed, so it was project-reported, not reproducible. That was the honest but weak position.&lt;/p&gt;

&lt;p&gt;This is the strong one:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DAST-APPLICABLE  TP 987  FP 0  FN 169  TN 1102  P 1.000  R 0.854  F1 0.921
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Reproduced on &lt;strong&gt;v7.3.0&lt;/strong&gt; against the same pinned target (OWASP Benchmark commit &lt;code&gt;20cbf3d111…&lt;/code&gt;), under the pre-registered methodology in &lt;a href="https://github.com/Hetaireia/janissary/blob/main/BENCHMARK.md" rel="noopener noreferrer"&gt;&lt;code&gt;BENCHMARK.md&lt;/code&gt;&lt;/a&gt;. Amendment 21 records the protocol, the deviation from the 3-run baseline (documented, with reasoning), and the exact reproduce command.&lt;/p&gt;

&lt;p&gt;Two numbers, both traceable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Single-pass baseline: F1 0.902&lt;/strong&gt; (TP 950 / FN 206)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FN-recovery at &lt;code&gt;JANISSARY_TIMEOUT=180&lt;/code&gt;: F1 0.921&lt;/strong&gt; (TP 987 / FN 169)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The 0.902 to 0.921 jump is the Amendment 20 recovery protocol: re-scan the misses at a longer timeout, merge, re-score. Documented in the pre-registration before the run, not after.&lt;/p&gt;

&lt;p&gt;Delta vs Amendment 20 (v7.2.0: F1 0.920, TP 984): &lt;strong&gt;+3 TP, +0.001 F1&lt;/strong&gt; — within the ±13 TP run-to-run noise floor stated in &lt;code&gt;BENCHMARK.md&lt;/code&gt; line 654. The claim reproduces.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The receipt:&lt;/strong&gt; all three run directories committed under &lt;a href="https://github.com/Hetaireia/janissary/tree/main/benchmark/track_a/runs" rel="noopener noreferrer"&gt;&lt;code&gt;benchmark/track_a/runs/&lt;/code&gt;&lt;/a&gt;. To verify:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python benchmark/track_a/score_dast_aware.py benchmark/track_a/runs/20261024T130735Z-janissary-r1+rec
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;That's it. You should get TP 987 / FN 169 / F1 0.921. If you don't, the claim is wrong and you should say so.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it's good at
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Deduplication.&lt;/strong&gt; DVWA run: 8 CRITICAL signals grouped into 1 finding. That's the thing triage-fatigued users actually need.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WAF evidence.&lt;/strong&gt; Live ModSecurity: pacer backed off to 30.0s across 38 events. Observed backoff behavior, not a claimed bypass.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Framework-specific SQLi.&lt;/strong&gt; Paren-closing payloads verified live.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What it misses — read before installing
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SPA catch-all false positives.&lt;/strong&gt; 39 FPs on a Node SPA in the Juice Shop run. WordPress-admin-module rule fires on catch-all routing. Issue #1.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;401 aborts the scan for that path.&lt;/strong&gt; Auth-gated targets can hide findings downstream. Issue #2.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pathtraver recall 0.049–0.308.&lt;/strong&gt; Not a weak result — an unsupported class. Do not use JANISSARY for path traversal in 7.x. Use something else.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One case still times out at 180s&lt;/strong&gt; (&lt;code&gt;BenchmarkTest02321&lt;/code&gt;, xss-04, param_name). Counted as FN. Documented in Amendment 21.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;8.x gets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Issue #4 — generalize the six-oracle pattern to close the misattribution class.&lt;/li&gt;
&lt;li&gt;Issue #5 — &lt;code&gt;--mode ci&lt;/code&gt;, fast and unpaced for trusted pipelines.&lt;/li&gt;
&lt;li&gt;Payload-set generalization for framework-wrapped queries.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;7.x gets hotfixes only.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker pull oraclemode337/janissary:7.3.0
pip install janissary==7.3.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;443 tests passing. &lt;code&gt;main&lt;/code&gt; is clean at the tag.&lt;/p&gt;

&lt;p&gt;If 7.3.0 gets something wrong, open an issue. The ledger is public.&lt;/p&gt;

</description>
      <category>security</category>
      <category>appsec</category>
      <category>opensource</category>
    </item>
    <item>
      <title>https://dev.to/new</title>
      <dc:creator>Oracle Mode</dc:creator>
      <pubDate>Mon, 19 Oct 2026 20:21:04 +0000</pubDate>
      <link>https://dev.to/janissary/httpsdevtonew-3k5j</link>
      <guid>https://dev.to/janissary/httpsdevtonew-3k5j</guid>
      <description>&lt;h1&gt;
  
  
  Why I built a differential DAST scanner instead of using Nuclei or Burp
&lt;/h1&gt;

&lt;p&gt;When I started building JANISSARY, I didn't want to build another directory fuzzer or a script that blindly throws payloads at a wall. I wanted to build something that respects the operator's time.&lt;/p&gt;

&lt;p&gt;Most automated DAST tools fall into two camps: they either generate massive amounts of noise (high false positives), or they get instantly IP-banned by WAFs the moment they start scanning.&lt;/p&gt;

&lt;p&gt;Here is why I took a different architectural approach.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Differential Detection (Precision = 1.000)
&lt;/h3&gt;

&lt;p&gt;Nuclei is fantastic for CVE signature matching, but blind payload fuzzing drowns you in false positives. JANISSARY uses differential detection. Every finding is gated by a baseline comparison. If a single response changes, it doesn't emit a finding. It only fires when multiple oracles (status changes, length anomalies, error signatures) agree.&lt;/p&gt;

&lt;p&gt;On the OWASP Benchmark DAST-applicable corpus (2,258 cases), this architecture scored an F1 of 0.920 with a &lt;strong&gt;Precision of 1.000&lt;/strong&gt;. Zero false positives.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. WAF-Aware Pacing
&lt;/h3&gt;

&lt;p&gt;If you are scanning an authorized target, getting blocked mid-scan wastes your time. I ran JANISSARY against OWASP Juice Shop sitting behind a ModSecurity CRS WAF. The scanner detected the WAF, started hitting block signals, and dynamically backed off. It ramped the delay up to 30 seconds, recovered on a clean streak, and still successfully found the SQL injection without aborting the scan.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Burp-Native Output
&lt;/h3&gt;

&lt;p&gt;I don't want to fight with proprietary dashboards. JANISSARY outputs findings as JSONL with raw HTTP/1.1 requests and responses (CRLF line endings). You can pipe it straight into jq or paste the raw requests directly into Burp Suite Repeater to validate the finding manually.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Reality Check
&lt;/h3&gt;

&lt;p&gt;I recently ran JANISSARY against DVWA (Damn Vulnerable Web Application). It successfully grouped 8 distinct signals into a single CRITICAL SQL injection finding.&lt;/p&gt;

&lt;p&gt;It’s not perfect. I document the known limitations openly in the repo—like how SPAs can cause admin-panel false positives, or how 401s abort the scan. But it is honest, it is measured, and it is fast.&lt;/p&gt;

&lt;p&gt;You can try it out via Docker or PyPI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; oraclemode337/janissary scan &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="s2"&gt;"http://target.example"&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; q
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Repo: &lt;a href="https://github.com/Hetaireia/janissary" rel="noopener noreferrer"&gt;https://github.com/Hetaireia/janissary&lt;/a&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>opensource</category>
      <category>python</category>
      <category>devops</category>
    </item>
    <item>
      <title>0.920 on the benchmark. Zero findings on Juice Shop.</title>
      <dc:creator>Oracle Mode</dc:creator>
      <pubDate>Mon, 19 Oct 2026 18:02:40 +0000</pubDate>
      <link>https://dev.to/janissary/0920-on-the-benchmark-zero-findings-on-juice-shop-2nf8</link>
      <guid>https://dev.to/janissary/0920-on-the-benchmark-zero-findings-on-juice-shop-2nf8</guid>
      <description>&lt;p&gt;Three days ago I published JANISSARY's benchmark results: F1 0.920, precision 1.000 on the OWASP Benchmark DAST-applicable subset. Zero false positives across 2,258 cases.&lt;/p&gt;

&lt;p&gt;Today I ran it against OWASP Juice Shop. It found 39 vulnerabilities. All 39 were wrong. And it missed the SQL injection I already knew was in the search endpoint.&lt;/p&gt;

&lt;p&gt;Both of those things are true. This post is about why.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the benchmark actually promises
&lt;/h2&gt;

&lt;p&gt;The benchmark tests detection on a corpus of Java servlets. Each servlet either is or isn't vulnerable to a specific class. The scanner gets no source access. JANISSARY scored precision 1.000 — not a single finding on the corpus that wasn't a real vulnerability.&lt;/p&gt;

&lt;p&gt;That number is real. It's reproducible. The run is in the repo.&lt;/p&gt;

&lt;p&gt;But a benchmark measures one thing: how the tool behaves on the corpus. It doesn't promise the tool behaves the same way on a Node app.&lt;/p&gt;

&lt;p&gt;That distinction is the entire post.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happened on Juice Shop
&lt;/h2&gt;

&lt;p&gt;Juice Shop is a Node/Express single-page app. It's the canonical intentionally-vulnerable target — thousands of people use it to practice web exploitation.&lt;/p&gt;

&lt;p&gt;I ran:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;janissary agent http://localhost:3000 --attack-confirm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Output:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Platforms:       wordpress
Modules run:     xmlrpc, admin
Modules skipped: scan
New findings:    39
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Two things are wrong before you even look at the findings. Juice Shop isn't WordPress. And the &lt;code&gt;scan&lt;/code&gt; module — the one with the differential oracles that produced the benchmark result — never ran.&lt;/p&gt;

&lt;p&gt;The platform fingerprinter classified a Node app as WordPress. The agent then ran WordPress modules. The injection oracles sat this one out.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 39 findings
&lt;/h2&gt;

&lt;p&gt;All 39 had the same shape:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;category:        admin
finding_type:    exposed-panel
discriminator:   /wp-admin/
detail:          wordpress /wp-admin/ -&amp;gt; HTTP 200
response_status: 200
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Juice Shop's SPA router returns HTTP 200 and index.html for any path, including paths that don't exist. The admin module checked the status code, saw 200, and reported an exposed WordPress admin panel.&lt;/p&gt;

&lt;p&gt;On a single-page app, every path returns 200. Every path was reported.&lt;/p&gt;

&lt;p&gt;This is exactly the class of false positive JANISSARY's differential gating is designed to prevent. It fired anyway — because the admin module doesn't go through the differential gate. It's a status-code check wearing a scanner's clothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The miss
&lt;/h2&gt;

&lt;p&gt;Juice Shop's &lt;code&gt;/rest/products/search?q=&lt;/code&gt; endpoint is a documented SQL injection challenge. It's the first thing anyone tries.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;janissary scan -u "http://localhost:3000/rest/products/search?q=apple" -p q
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Output:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Total requests:  40
Findings:        0 in 0 groups
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The scan ran. The baseline was stable. The WAF probe was clean. Nothing fired.&lt;/p&gt;

&lt;p&gt;Here's the query Juice Shop builds internally:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT * FROM Products
WHERE ((name LIKE '%apple%' OR description LIKE '%apple%')
AND deletedAt IS NULL)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;To inject, you have to close two parens first:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apple')) UNION SELECT ...--
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;JANISSARY's payload set is benchmark-shaped. It sends &lt;code&gt;'&lt;/code&gt;, &lt;code&gt;' OR '1'='1&lt;/code&gt;, and similar. Those are correct for the benchmark's servlets, which do simple string concatenation. On Juice Shop, &lt;code&gt;'&lt;/code&gt; produces a syntax error that the app swallows, returning an empty result set. No differential delta. No finding.&lt;/p&gt;

&lt;p&gt;The payload set doesn't know about framework-wrapped queries.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this says about benchmark numbers
&lt;/h2&gt;

&lt;p&gt;The 0.920 is not fake. It measures what it says it measures: how JANISSARY performs on a corpus of Java servlets with simple injection points.&lt;/p&gt;

&lt;p&gt;What it doesn't measure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Non-Java platforms&lt;/li&gt;
&lt;li&gt;Queries wrapped in parentheses or built by ORMs&lt;/li&gt;
&lt;li&gt;SPAs with catch-all routing&lt;/li&gt;
&lt;li&gt;Endpoints behind authentication&lt;/li&gt;
&lt;li&gt;Any error output that doesn't match the benchmark's signature set&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those aren't edge cases. They're most of the web.&lt;/p&gt;

&lt;p&gt;So the honest reading of "F1 0.920" is: &lt;em&gt;"this tool performs at 0.920 on a well-defined class of applications with well-defined vulnerability shapes."&lt;/em&gt; The class is narrower than "web applications." It's roughly "Java servlet-style apps with simple injection points."&lt;/p&gt;

&lt;p&gt;That's still a real result. It's just not the result I implied by publishing the number without qualification.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'm doing about it
&lt;/h2&gt;

&lt;p&gt;Three issues, all public:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/Hetaireia/janissary/issues/1" rel="noopener noreferrer"&gt;#1&lt;/a&gt; — admin module reports false positives on SPA catch-all routing&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/Hetaireia/janissary/issues/3" rel="noopener noreferrer"&gt;#3&lt;/a&gt; — SQLi payload set lacks framework-specific variants (paren-closing, comment terminators)&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/Hetaireia/janissary/issues/2" rel="noopener noreferrer"&gt;#2&lt;/a&gt; — scan aborts on HTTP 401 without reporting auth-required state&lt;br&gt;
None of these are launch-killers. All of them are fixable. The fixes are specific, small, and testable:&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The admin probe should verify response body shape, not just status&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The payload library should include &lt;code&gt;'))&lt;/code&gt;, &lt;code&gt;--&lt;/code&gt;, &lt;code&gt;/* */&lt;/code&gt;, and quote-stacking variants&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;401 responses should report "endpoint requires authentication" instead of aborting silently&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each fix has a reproduction case from Juice Shop. Each can be verified against the same target.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest version of the claim
&lt;/h2&gt;

&lt;p&gt;If you're evaluating JANISSARY on the strength of the benchmark number, here's what you should actually believe:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;On applications that look like the OWASP Benchmark's servlets, it performs at F1 0.920 with zero false positives. That's measured.&lt;/li&gt;
&lt;li&gt;On applications that don't, its behavior is uncharacterized. As of today, the first live-target run produced false positives from one module and misses from another.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The tool's whole thesis is that findings should be reproducible differences, not pattern matches. That thesis has to apply to the tool's own claims. The benchmark number was a pattern match on a corpus. The Juice Shop run is the difference.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;JANISSARY is open source (Apache 2.0). Repo: &lt;a href="https://github.com/Hetaireia/janissary" rel="noopener noreferrer"&gt;https://github.com/Hetaireia/janissary&lt;/a&gt;. Issues: &lt;a href="https://github.com/Hetaireia/janissary/issues" rel="noopener noreferrer"&gt;https://github.com/Hetaireia/janissary/issues&lt;/a&gt;. Benchmark methodology: BENCHMARK.md.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>testing</category>
      <category>opensource</category>
      <category>python</category>
    </item>
    <item>
      <title>We refuse to emit a finding from a single response</title>
      <dc:creator>Oracle Mode</dc:creator>
      <pubDate>Tue, 13 Oct 2026 08:21:28 +0000</pubDate>
      <link>https://dev.to/janissary/we-refuse-to-emit-a-finding-from-a-single-response-3bld</link>
      <guid>https://dev.to/janissary/we-refuse-to-emit-a-finding-from-a-single-response-3bld</guid>
      <description>&lt;p&gt;Every DAST tool I've used has the same failure mode, and it isn't detection.&lt;br&gt;
It's trust.&lt;/p&gt;

&lt;p&gt;You run a scan. You get 200 findings. You triage 30. You discover 22 are&lt;br&gt;
noise. You stop reading findings. The tool that was supposed to save you&lt;br&gt;
time now costs you time, and the next time you run it you skim.&lt;/p&gt;

&lt;p&gt;I built JANISSARY around one rule: &lt;strong&gt;a finding that isn't a difference is a&lt;br&gt;
guess.&lt;/strong&gt; Here's what that means in practice, why it's harder than it sounds,&lt;br&gt;
and what we gave up to ship it.&lt;/p&gt;


&lt;h2&gt;
  
  
  What "differential" actually requires
&lt;/h2&gt;

&lt;p&gt;A finding should never be emitted from a single response. That sounds like a&lt;br&gt;
one-line policy. It's actually four engineering problems stacked on top of&lt;br&gt;
each other.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First, you need a baseline you can trust.&lt;/strong&gt; Before probing, JANISSARY&lt;br&gt;
sends N benign requests to the same endpoint, same parameter, same headers.&lt;br&gt;
If the baseline isn't stable — if the response time varies by more than a&lt;br&gt;
threshold, if the body length jumps between runs — the tool refuses to&lt;br&gt;
engage the timing gates and tells you so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[q] baseline is static - timing gates disabled
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's not a bug, it's the tool being honest. A flaky baseline produces&lt;br&gt;
flaky deltas, and flaky deltas produce false positives. Better to run&lt;br&gt;
fewer gates than to run wrong ones.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Second, you need a probe that produces a &lt;em&gt;reproducible&lt;/em&gt; delta.&lt;/strong&gt; Firing&lt;br&gt;
a payload and seeing the response change isn't enough. The change has to be&lt;br&gt;
consistent, tied to the payload, and not explained by normal variance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Third, you need to distinguish a delta from a WAF block.&lt;/strong&gt; Cloudflare&lt;br&gt;
returns a 403 with a challenge page — that's not an SQL injection, that's&lt;br&gt;
Cloudflare doing its job. JANISSARY's pacer watches for those signatures&lt;br&gt;
and feeds them into the differential gate so a block doesn't get reported&lt;br&gt;
as a vulnerability.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fourth, you need to normalise time-varying responses.&lt;/strong&gt; CSRF tokens,&lt;br&gt;
nonces, timestamps, session IDs, dynamic ad copy — the response body&lt;br&gt;
changes constantly for reasons that have nothing to do with your payload.&lt;br&gt;
Comparison has to strip those out or the gate fires on everything.&lt;/p&gt;

&lt;p&gt;Each of those is a separate subsystem. Together, they produce output that&lt;br&gt;
looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  Findings:
    F-001  CRITICAL sqli:sql_injection  (param=q)
           - [sql_single_quote] db_error: SQLITE error signature matched
           - [sql_single_quote] status_change: Baseline 200 -&amp;gt; Payload 500
           - [sql_or_1eq1]      length_anomaly: Response 3.2x baseline
           - [sql_union_null]   status_change: Baseline 200 -&amp;gt; Payload 500
           - [sql_union_null]   length_anomaly: Response 8.3x baseline
           - [sql_sleep_mysql]  status_change: Baseline 200 -&amp;gt; Payload 500
           - [sql_sleep_pg]     status_change: Baseline 200 -&amp;gt; Payload 500
           - [sql_sleep_pg]     length_anomaly: Response 4.7x baseline

    F-002  LOW      sqli:reflection  (param=q)
           - [sql_single_quote] payload_reflected: Payload reflected (raw)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Eleven signals. &lt;strong&gt;One bug.&lt;/strong&gt; Every piece of evidence for &lt;code&gt;F-001&lt;/code&gt; points at&lt;br&gt;
the same SQL injection, so JANISSARY groups them. &lt;code&gt;F-002&lt;/code&gt; is a different&lt;br&gt;
root cause — reflection — and stays separate.&lt;/p&gt;

&lt;p&gt;The first version of this scan reported nine findings. A reviewer looking&lt;br&gt;
at the summary saw &lt;code&gt;9 findings&lt;/code&gt; and moved on. Now they see &lt;code&gt;2 groups&lt;/code&gt; and&lt;br&gt;
read the one that matters.&lt;/p&gt;


&lt;h2&gt;
  
  
  What we gave up
&lt;/h2&gt;

&lt;p&gt;We gave up speed.&lt;/p&gt;

&lt;p&gt;A baseline-gated scan runs the target 3–5x more than a naive scanner. On a&lt;br&gt;
100-endpoint target, that's the difference between a 4-minute scan and a&lt;br&gt;
12-minute scan. We also gave up recall: when the baseline is unstable, we&lt;br&gt;
disable the gates and miss findings rather than flood the report with&lt;br&gt;
maybes.&lt;/p&gt;

&lt;p&gt;We think that's the right trade. Here's the number that convinced us:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;On two local vulnerable Flask endpoints (SQL injection and reflected&lt;br&gt;
XSS), JANISSARY emitted &lt;strong&gt;23 findings across 3 groups&lt;/strong&gt; — and the&lt;br&gt;
SQL-injection scan returned the &lt;em&gt;identical&lt;/em&gt; finding set across two&lt;br&gt;
back-to-back runs. A naive scan of the same endpoints produces 23&lt;br&gt;
signals with no grouping, so the same bug shows up as 11 separate&lt;br&gt;
findings, and triage starts at the wrong number.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That last word — &lt;em&gt;reproducible&lt;/em&gt; — is the one most DAST tools can't claim.&lt;br&gt;
Run the same scan twice and compare the output. If your tool's finding set&lt;br&gt;
changes between identical runs, it isn't gating on differences, and you're&lt;br&gt;
paying triage tax on every scan.&lt;/p&gt;


&lt;h2&gt;
  
  
  Does any of this hold at scale?
&lt;/h2&gt;

&lt;p&gt;The Flask demo is a demo. Here's a harder test: OWASP Benchmark, the&lt;br&gt;
standard open benchmark for DAST tools.&lt;/p&gt;

&lt;p&gt;It contains 2,740 test cases across SQL injection, XSS, command&lt;br&gt;
injection, path traversal, LDAP injection, XPath injection, weak&lt;br&gt;
randomness, trust-boundary violations, and secure-cookie flags. Each&lt;br&gt;
case is a servlet that either is or isn't vulnerable to a specific&lt;br&gt;
class. The scanner has to figure out which without source access.&lt;/p&gt;

&lt;p&gt;On the DAST-applicable subset — the corpus minus the crypto and hash&lt;br&gt;
cases that only a source-aware tool can observe — JANISSARY scores:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;F1        0.920
Precision 1.000
Recall    0.851
TP        984
FP        0
FN        172
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Eighteen of the DAST-applicable categories score F1 1.000. Six more&lt;br&gt;
score F1 0.95 or above. The weakest category is pathtraver, where the&lt;br&gt;
F1 range across four subcategories is 0.049 to 0.308 — and there's a&lt;br&gt;
separate write-up on why that's a property of the benchmark target,&lt;br&gt;
not of the scanner: &lt;a href="https://github.com/Hetaireia/janissary/blob/main/docs/pathtraver.md" rel="noopener noreferrer"&gt;&lt;code&gt;docs/pathtraver.md&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The number that matters most here is Precision 1.000.&lt;/strong&gt; Not a single&lt;br&gt;
false positive across the whole DAST-applicable corpus. That's the&lt;br&gt;
design claim — a finding that isn't a difference is a guess — turned&lt;br&gt;
into a measured outcome.&lt;/p&gt;

&lt;p&gt;A scanner that emits zero false positives on a corpus this size isn't&lt;br&gt;
"tuned." It's architected that way. Every oracle requires evidence of&lt;br&gt;
a specific kind: a real database error signature, a session sink echo&lt;br&gt;
of a per-scan random marker, a raw XSS payload past its trailing&lt;br&gt;
character, a paired differential on a file-existence oracle. Nothing&lt;br&gt;
fires on pattern match alone.&lt;/p&gt;

&lt;p&gt;The 0.920 recall is the honest number. There are 172 cases JANISSARY&lt;br&gt;
doesn't catch. 123 are the pathtraver target-cap; the rest are&lt;br&gt;
scattered gaps across subcategories that we triage one at a time. The&lt;br&gt;
full breakdown, including the cases we miss, is in &lt;code&gt;BENCHMARK.md&lt;/code&gt;.&lt;br&gt;
The point of running a benchmark is to learn where the tool fails,&lt;br&gt;
not to have a number.&lt;/p&gt;

&lt;p&gt;The run is reproducible:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python benchmark/track_a/run_with_recovery.py       --target-base http://127.0.0.1:8080/benchmark       --cases-csv benchmark/track_a/_chunks/full_p0.csv       --tools-env benchmark/track_a/tools_env_default.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Requires the OWASP Benchmark container; the script and the container&lt;br&gt;
image are in the repo.&lt;/p&gt;
&lt;h3&gt;
  
  
  How do other tools do?
&lt;/h3&gt;

&lt;p&gt;We ran Nuclei 3.11 and ZAP baseline against the same corpus (chunk 0,&lt;br&gt;
550 URLs). Neither is a direct competitor to JANISSARY — they answer&lt;br&gt;
different questions — but the comparison is instructive.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Benchmark TPs&lt;/th&gt;
&lt;th&gt;Benchmark FPs&lt;/th&gt;
&lt;th&gt;Findings on the corpus&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;JANISSARY&lt;/td&gt;
&lt;td&gt;984 / 1156&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;2914&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nuclei 3.11&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ZAP baseline&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;27&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Nuclei's templates are keyed on known CVEs and version fingerprints.&lt;br&gt;
The benchmark uses neither. Its 0 findings aren't a failure — they're a&lt;br&gt;
measurement of scope. Nuclei is a vulnerability-discovery tool. The&lt;br&gt;
OWASP Benchmark is not a discovery target.&lt;/p&gt;

&lt;p&gt;ZAP baseline is passive-only: it spiders and reads. It found missing&lt;br&gt;
security headers, missing anti-CSRF tokens, CSP weaknesses, and error&lt;br&gt;
disclosures — all real signals about the benchmark's Tomcat setup,&lt;br&gt;
none of them the injection classes the benchmark scores. Active&lt;br&gt;
scanning (ZAP full) is the closer comparison, and its recall on&lt;br&gt;
synthetic injection cases is historically below 20%. JANISSARY's&lt;br&gt;
F1 0.920 is a specific outcome of the differential design, not a&lt;br&gt;
category-wide win for "scanners."&lt;/p&gt;

&lt;p&gt;The honest takeaway: &lt;strong&gt;these tools are complementary.&lt;/strong&gt; Run Nuclei&lt;br&gt;
for CVE coverage. Run ZAP for passive hygiene findings. Run JANISSARY&lt;/p&gt;

&lt;p&gt;Update (Oct 18): I ran JANISSARY against a live target. It produced 39 false positives and missed a known SQLi. Here's the honest post-mortem: &lt;a href="https://dev.to/janissary/0920-on-the-benchmark-zero-findings-on-juice-shop-3g4d-temp-slug-8188118/"&gt;https://dev.to/janissary/0920-on-the-benchmark-zero-findings-on-juice-shop-3g4d-temp-slug-8188118/&lt;/a&gt;.&lt;br&gt;
for reproducible differential detection on the injection classes.&lt;br&gt;
They don't compete for the same triage slot.&lt;/p&gt;


&lt;h2&gt;
  
  
  What this means if you're evaluating DAST
&lt;/h2&gt;

&lt;p&gt;Ask your current tool one question: &lt;strong&gt;if I run this scan twice, do I get&lt;br&gt;
the same findings?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If the answer is no, differential gating isn't in it, and the tool is&lt;br&gt;
producing guesses. That's not a moral judgment — a guess is useful if&lt;br&gt;
you're hunting for leads. It's a problem if you're handing the output to a&lt;br&gt;
client, an auditor, or your own backlog and expecting it to be actionable&lt;br&gt;
without re-verification.&lt;/p&gt;

&lt;p&gt;JANISSARY is the precision tool in your toolbox. It won't find everything.&lt;br&gt;
It will only tell you what it can prove.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;janissary
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/Hetaireia/janissary" rel="noopener noreferrer"&gt;github.com/Hetaireia/janissary&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;JANISSARY is a dual-use security testing tool. Run it only against&lt;br&gt;
systems you own or have prior written permission to test. See the&lt;br&gt;
&lt;a href="https://github.com/Hetaireia/janissary/blob/main/LEGAL.md" rel="noopener noreferrer"&gt;Terms of Use&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>python</category>
      <category>opensource</category>
      <category>testing</category>
    </item>
  </channel>
</rss>
