<?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: Regaan</title>
    <description>The latest articles on DEV Community by Regaan (@regaan).</description>
    <link>https://dev.to/regaan</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%2F3831395%2F43dda7f0-3ab9-4ab3-b7f8-762a5cfafa6d.png</url>
      <title>DEV Community: Regaan</title>
      <link>https://dev.to/regaan</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/regaan"/>
    <language>en</language>
    <item>
      <title>Why I stopped treating WebSocket messages as the unit of work (and built WSHawk instead)</title>
      <dc:creator>Regaan</dc:creator>
      <pubDate>Sat, 11 Jul 2026 09:29:50 +0000</pubDate>
      <link>https://dev.to/regaan/why-i-stopped-treating-websocket-messages-as-the-unit-of-work-and-built-wshawk-instead-5137</link>
      <guid>https://dev.to/regaan/why-i-stopped-treating-websocket-messages-as-the-unit-of-work-and-built-wshawk-instead-5137</guid>
      <description>&lt;p&gt;Most automated web scanners are built around one assumption: you send a request, you get a response, you look at the response. That model falls apart the moment you point them at a WebSocket endpoint.&lt;/p&gt;

&lt;p&gt;I kept running into this while testing realtime apps (chat, collaboration tools, trading dashboards, the usual SaaS suspects), so I built &lt;strong&gt;WSHawk&lt;/strong&gt;, an open-source toolkit (v4.0.1, AGPL-3.0) for authorized security assessment of WebSocket and realtime web applications. This post is a tour of &lt;em&gt;why&lt;/em&gt; each piece exists and how it fits together.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;WSHawk is an offensive tool. It's for authorized testing only: systems you own, engagements you're contracted for, or bug bounty programs within scope. Full stop.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why WebSockets break normal scanners
&lt;/h2&gt;

&lt;p&gt;After the HTTP upgrade handshake, a WebSocket is just a long-lived, full-duplex TCP channel. Three things about that make classic tooling useless:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The connection is stateful.&lt;/strong&gt; Auth, subscriptions, and app context are built up through an &lt;em&gt;ordered&lt;/em&gt; sequence of messages. You can't just fire a payload at a URL, you have to connect, authenticate, subscribe, and &lt;em&gt;then&lt;/em&gt; reach the injectable sink, all on the same socket.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Communication is async and bidirectional.&lt;/strong&gt; A server's reaction to your injected message might be delayed, interleaved with unrelated push traffic, or arrive on a completely different channel. Sometimes it never comes back on the same socket at all.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Payloads are structured.&lt;/strong&gt; JSON, XML, Protocol Buffers, MessagePack, CBOR. Throw a raw &lt;code&gt;' OR 1=1--&lt;/code&gt; at a schema-validated message and it gets rejected long before it reaches anything interesting.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So the core design decision in WSHawk is this: &lt;strong&gt;the connection, not the individual message, is the unit of work.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Learn first, attack second
&lt;/h2&gt;

&lt;p&gt;The scanner splits into a passive &lt;strong&gt;learning phase&lt;/strong&gt; and active &lt;strong&gt;probing phases&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;During learning, WSHawk keeps the socket open (five seconds by default), samples the traffic, and figures out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the wire format and which fields are injectable,&lt;/li&gt;
&lt;li&gt;the backend stack (it fingerprints languages, frameworks, and databases from response signatures),&lt;/li&gt;
&lt;li&gt;a response baseline for later comparison.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Only &lt;em&gt;after&lt;/em&gt; it understands the schema does it run the per-class probes: SQLi, XSS, command injection, path traversal, XXE. Each probe injects into a &lt;strong&gt;well-formed&lt;/strong&gt; base message rather than spraying raw strings, which is the whole point: you have to look like valid traffic to get past structural validation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Connect + state sequence
   -&amp;gt; Learning phase (sample, detect format, fingerprint)
   -&amp;gt; Per-class probing (SQLi, XSS, CMDi, XXE, traversal)
   -&amp;gt; Heuristic verifier =&amp;gt; confidence
   -&amp;gt; High-confidence XSS? -&amp;gt; browser verify
   -&amp;gt; CVSS + report / evidence
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Payloads that adapt to the server
&lt;/h2&gt;

&lt;p&gt;There are two payload subsystems working together.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. A strategy-scored mutation engine.&lt;/strong&gt; Eight deterministic strategy families (encoding, case variation, comment injection, whitespace substitution, concatenation, filter-bypass, tag-breaking, polyglot). Each carries a weighted score. When a payload gets through unblocked, every strategy it used gets boosted. It also reads the room: it scans responses for WAF signatures (Cloudflare, Akamai, Imperva, F5, AWS WAF, ModSecurity) and generic block indicators, then steers future mutations around whatever's filtering.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Smart Payload Evolution (SPE)&lt;/strong&gt;, a genetic pipeline:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a &lt;strong&gt;context-aware generator&lt;/strong&gt; that learns the message structure and embeds attack strings inside well-formed JSON/XML (it even emits type-confusion payloads like &lt;code&gt;__proto__&lt;/code&gt; prototype-pollution objects),&lt;/li&gt;
&lt;li&gt;a &lt;strong&gt;feedback loop&lt;/strong&gt; that classifies each response into &lt;code&gt;ERROR&lt;/code&gt;, &lt;code&gt;BLOCKED&lt;/code&gt;, &lt;code&gt;REFLECTED&lt;/code&gt;, &lt;code&gt;DELAYED&lt;/code&gt;, &lt;code&gt;DIFFERENT&lt;/code&gt;, or &lt;code&gt;NORMAL&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;an &lt;strong&gt;evolver&lt;/strong&gt; that treats successful payloads as a population, breeds them with crossover/mutation, and reallocates effort toward high-fitness individuals.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Fitness is an exponential moving average, with guardrails so it doesn't run away:&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="c1"&gt;# fitness update (EMA)
&lt;/span&gt;&lt;span class="n"&gt;f_new&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.4&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.6&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;f_old&lt;/span&gt;      &lt;span class="c1"&gt;# beta = 0.4
# anything scoring &amp;gt;= 0.7 goes to the hall of fame
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Offspring are MD5-deduplicated, capped at 2000 chars, and the population is trimmed back to N=100 fittest each generation, so memory stays bounded no matter how long a run goes.&lt;/p&gt;

&lt;p&gt;Here's the actual crossover operator (abridged):&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_crossover&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p2&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;strategy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;split&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;interleave&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;wrap&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;inject&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;strategy&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;split&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;m1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;
        &lt;span class="nf"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p1&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="n"&gt;m1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;p2&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;m2&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;
                &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;p2&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="n"&gt;m2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;p1&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;m1&lt;/span&gt;&lt;span class="p"&gt;:])&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;strategy&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;interleave&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p2&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p1&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;p2&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;strategy&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;wrap&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;p1&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;p2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;p1&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt;
    &lt;span class="c1"&gt;# inject: splice p2 at a random offset in p1
&lt;/span&gt;    &lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;p1&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;p2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;p1&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Stacked oracles: cheap to expensive
&lt;/h2&gt;

&lt;p&gt;Naive detection lies to you. Reflection isn't execution, blind bugs give no on-channel signal, and timing is noisy. So WSHawk layers its oracles by cost:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Heuristic verifier&lt;/strong&gt; first (error signatures, timing, reflection with context analysis). One nice detail: timing uses &lt;code&gt;time.monotonic()&lt;/code&gt;, not wall-clock, so a system clock change can't poison a time-based detection.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Out-of-band (OAST) callbacks&lt;/strong&gt; for blind XXE and SSRF, the payload makes the target phone home to a collaborator; the callback is the proof.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A Playwright/Chromium browser pool&lt;/strong&gt; for XSS, but &lt;em&gt;only&lt;/em&gt; on high-confidence candidates. It renders the response in a sandboxed page that overrides &lt;code&gt;alert&lt;/code&gt;/&lt;code&gt;eval&lt;/code&gt;, installs a &lt;code&gt;MutationObserver&lt;/code&gt;, and confirms &lt;em&gt;actual script execution&lt;/em&gt; rather than mere reflection.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The browser oracle is expensive, so gating it behind the heuristic verifier's HIGH rating is a deliberate cost/precision tradeoff.&lt;/p&gt;

&lt;h2&gt;
  
  
  HTTP and WebSocket, same workflow engine
&lt;/h2&gt;

&lt;p&gt;Modern apps split state across a browser-authenticated HTTP surface and a realtime socket. So WSHawk gives HTTP and WebSocket &lt;strong&gt;symmetric&lt;/strong&gt; attack services, replay, authorization-diffing, race testing, subscription abuse, all sharing the same identities and a &lt;code&gt;{{variable}}&lt;/code&gt; templating workflow engine.&lt;/p&gt;

&lt;p&gt;That's what lets a single playbook chain an HTTP bootstrap into a privileged WebSocket action:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP bootstrap (extract CSRF/tenant)
   -&amp;gt; variable map {{var}}
   -&amp;gt; WS replay (privileged action)
   -&amp;gt; bundle with HTTP + WS replay recipes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Six playbooks ship built-in: login bootstrap, CSRF/session capture, WS privilege escalation, stale-token reuse, tenant hopping, and authed HTTP replay.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;race tester&lt;/strong&gt; is worth a mention: it launches N attempts per wave, all blocked on a shared &lt;code&gt;asyncio.Event&lt;/code&gt; barrier so they release as close to simultaneously as possible, then flags a suspicious window when more than one attempt succeeds in a state-changing mode (duplicate action, replay-before-invalidation, stale-token window).&lt;/p&gt;

&lt;h2&gt;
  
  
  Knowing what protocol you're actually looking at
&lt;/h2&gt;

&lt;p&gt;Realtime traffic is rarely "raw", it's usually one of a handful of recognizable families. WSHawk's protocol subsystem turns captured frames into an actionable map and fingerprints six families with dedicated target packs:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pack&lt;/th&gt;
&lt;th&gt;Signals&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;graphql_ws&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;subscribe/next/complete, operations, variables&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;phoenix_channels&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;topic/event/payload/join_ref&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actioncable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;command/identifier with embedded identifier JSON&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;signalr&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;record-separator framing, invocation IDs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;socket_io&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Engine.IO prefixes, namespaces, event arrays&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;binary_realtime&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;consumes protobuf/msgpack/CBOR analysis&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Once it knows the family, it recommends concrete attacks and maps them to playbooks the workflow engine can actually run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Evidence you can trust
&lt;/h2&gt;

&lt;p&gt;A finding is only as good as the trail behind it. Everything (identities, traffic, findings, notes, replay recipes) lives in a project-backed SQLite store, with sensitive columns encrypted at rest. Exports are &lt;strong&gt;tamper-evident&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Project bundle -&amp;gt; redaction (mask secrets) -&amp;gt; canonical JSON
   -&amp;gt; per-list hash chains + Ed25519 signature (SHA-256)
   -&amp;gt; integrity + provenance block
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The hash chain folds each item into the previous root, so any &lt;em&gt;reorder&lt;/em&gt; is detectable, and the Ed25519 signature over canonical JSON catches any &lt;em&gt;content edit&lt;/em&gt;. Verification is self-contained because the public key and chain roots travel inside the bundle. Redaction runs before export, so shared bundles don't leak secrets.&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="nd"&gt;@classmethod&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_hash_chain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;previous&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;previous&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_sha256_hex&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;previous&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;previous&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;item&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;count&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;root&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;previous&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The rest of the platform
&lt;/h2&gt;

&lt;p&gt;A quick lightning round of what else is in there:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Binary message handler&lt;/strong&gt; with format auto-detection (zlib/gzip, Avro, BSON, protobuf, FlatBuffers, MessagePack, CBOR, plus a Shannon-entropy test that flags likely-encrypted payloads at &amp;gt;7.5 bits/byte) and format-aware mutation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Endpoint discovery&lt;/strong&gt; that probes common WS paths, parses HTML/JS for &lt;code&gt;ws://&lt;/code&gt;/&lt;code&gt;wss://&lt;/code&gt; URLs and library signatures, and ranks hits by confidence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Session-hijacking tester&lt;/strong&gt; (token reuse, subscription spoofing, impersonation, channel violations, session fixation, privilege escalation).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A blue-team defensive suite&lt;/strong&gt; (DNS-exfil egress filtering, bot detection, CSWSH Origin enforcement, WSS/TLS posture checks).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A process-isolated plugin system&lt;/strong&gt;, plugins run in a separate process with a 5s timeout, size limits, and a sanitized env, so a crashing plugin returns a safe default instead of killing the scan.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Adaptive token-bucket rate limiting&lt;/strong&gt; that backs off on latency and WebSocket close-code signals (1013 = strong rate-limit signal), plus an HTTP resilience layer with retry and circuit breaking.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reporting&lt;/strong&gt; in JSON/CSV/SARIF 2.1.0/HTML, and integrations for Jira, DefectDojo (with per-class CWE mapping), and Slack/Discord/Teams webhooks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There's also an Electron desktop app (frame-by-frame interceptor, payload blaster, endpoint map, auth builder, evidence vault) and a Manifest V3 browser companion that pairs on localhost to ingest handshake context.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest part: what I haven't proven yet
&lt;/h2&gt;

&lt;p&gt;I want to be straight about this, because security tooling has a credibility problem when it overclaims.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;WSHawk does not yet have a detection-accuracy benchmark.&lt;/strong&gt; The current validation is engineering-focused: a unit/integration test suite and three local validation labs (a full-stack realtime SaaS, a Socket.IO SaaS, and a GraphQL-subscriptions lab) that verify &lt;em&gt;behavioral correctness and reproducibility&lt;/em&gt;. What they do &lt;strong&gt;not&lt;/strong&gt; measure is true/false-positive rates against a labeled vulnerability corpus, throughput comparisons, or head-to-head results against other scanners.&lt;/p&gt;

&lt;p&gt;Other limitations I'll name openly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the XSS browser oracle depends on Playwright/Chromium (no Playwright = heuristic-only fallback),&lt;/li&gt;
&lt;li&gt;CVSS scoring is a simplified v3.1 base-score computation, indicative, not a full calculator,&lt;/li&gt;
&lt;li&gt;non-browser classes lean on error/timing/reflection heuristics, which are inherently prone to false positives/negatives,&lt;/li&gt;
&lt;li&gt;binary parsing is heuristic and schema-less, so field extraction is best-effort.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Closing that empirical gap (a controlled evaluation with per-class oracle precision: heuristic vs. browser-verified vs. OAST-confirmed) is the top of the roadmap.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;If you're building or testing realtime apps, the mental shift that matters is treating the &lt;strong&gt;connection&lt;/strong&gt; as the thing you're testing, not individual messages. State preservation, schema-aware injection, layered oracles, and a signed evidence trail all fall out of that one decision.&lt;/p&gt;

&lt;p&gt;Repo's here if you want to dig in : &lt;strong&gt;&lt;a href="https://github.com/regaan/wshawk" rel="noopener noreferrer"&gt;https://github.com/regaan/wshawk&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Built by Regaan R // Rot Hackers. Authorized testing only, folks.&lt;/p&gt;

</description>
      <category>security</category>
      <category>websocket</category>
      <category>python</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How I Built an AI That Breeds Its Own Jailbreaks Using Genetic Algorithms</title>
      <dc:creator>Regaan</dc:creator>
      <pubDate>Wed, 18 Mar 2026 12:49:37 +0000</pubDate>
      <link>https://dev.to/regaan/how-i-built-an-ai-that-breeds-its-own-jailbreaks-using-genetic-algorithms-270l</link>
      <guid>https://dev.to/regaan/how-i-built-an-ai-that-breeds-its-own-jailbreaks-using-genetic-algorithms-270l</guid>
      <description>&lt;p&gt;Static jailbreak lists are dead.&lt;/p&gt;

&lt;p&gt;Every time a model provider patches their safety filters, your entire payload library becomes obsolete. Manual red teaming doesn't scale. And most AI security tools are just payload databases with a UI.&lt;/p&gt;

&lt;p&gt;So I built something different.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;I tested 6 major LLM deployments last year. Every single one had a bypass within 5 prompts. The problem isn't that LLMs are insecure — it's how the industry tests them.&lt;/p&gt;

&lt;p&gt;Most red teaming today looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Copy a jailbreak from a GitHub list&lt;/li&gt;
&lt;li&gt;Paste it into the target&lt;/li&gt;
&lt;li&gt;If it works, report it&lt;/li&gt;
&lt;li&gt;If it doesn't, try the next one&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's not security testing. That's pattern matching. And it stops working the moment the model gets patched.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Idea
&lt;/h2&gt;

&lt;p&gt;What if adversarial prompts could evolve?&lt;/p&gt;

&lt;p&gt;Not manually crafted. Not randomly generated. Actually &lt;strong&gt;evolved&lt;/strong&gt; — like organisms under selection pressure.&lt;/p&gt;

&lt;p&gt;The strong prompts survive. The weak ones die. The survivors mutate and reproduce. Each generation gets better at bypassing the target's specific defenses.&lt;/p&gt;

&lt;p&gt;That's the core idea behind &lt;strong&gt;Basilisk&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;p&gt;Basilisk introduces &lt;strong&gt;Smart Prompt Evolution for Natural Language (SPE-NL)&lt;/strong&gt; — a genetic algorithm that treats adversarial prompts as organisms in a population.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Selection:&lt;/strong&gt; Each prompt is scored by a multi-signal fitness function — did it bypass the guardrail? Did it extract sensitive data? Did it make the model contradict its system prompt?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mutation:&lt;/strong&gt; 10 mutation operators transform surviving prompts — semantic rewriting, context injection, authority spoofing, encoding shifts, persona layering, and more.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Crossover:&lt;/strong&gt; 5 crossover strategies combine the strongest parts of two successful prompts into offspring that inherit the best traits of both parents.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Evolution:&lt;/strong&gt; By generation 5, the evolved prompts achieved a 92% improvement in attack success rate over the original static payload library.&lt;/p&gt;

&lt;p&gt;The prompts literally get smarter at breaking the specific target they're aimed at.&lt;/p&gt;

&lt;h2&gt;
  
  
  What It Covers
&lt;/h2&gt;

&lt;p&gt;Basilisk maps to the OWASP LLM Top 10 with 29 attack modules across 8 categories:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prompt injection (direct and indirect)&lt;/li&gt;
&lt;li&gt;System prompt extraction&lt;/li&gt;
&lt;li&gt;Data exfiltration&lt;/li&gt;
&lt;li&gt;Tool/function abuse&lt;/li&gt;
&lt;li&gt;Guardrail bypass&lt;/li&gt;
&lt;li&gt;Denial of service&lt;/li&gt;
&lt;li&gt;Multi-turn manipulation&lt;/li&gt;
&lt;li&gt;RAG poisoning&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It supports 100+ LLM providers through LiteLLM — OpenAI, Anthropic, Google, Mistral, Cohere, local models via Ollama, and any custom endpoint.&lt;/p&gt;

&lt;h2&gt;
  
  
  Differential Testing
&lt;/h2&gt;

&lt;p&gt;One of my favorite features — point Basilisk at multiple models simultaneously and watch how they diverge.&lt;/p&gt;

&lt;p&gt;The same evolved prompt might bypass Claude but fail on GPT. Or break Gemini's guardrails while Llama holds firm. This behavioral divergence analysis reveals which models share defense architectures and which have unique weak points.&lt;/p&gt;

&lt;h2&gt;
  
  
  Non-Destructive Posture Assessment
&lt;/h2&gt;

&lt;p&gt;Not every engagement needs active exploitation. Basilisk includes a guardrail grading mode that scores your LLM's defenses from A+ to F without actually breaking anything. Safe enough for production environments.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Results
&lt;/h2&gt;

&lt;p&gt;On the benchmark targets I tested:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;92% improvement&lt;/strong&gt; in attack success rate by generation 5&lt;/li&gt;
&lt;li&gt;Evolved prompts discovered bypass patterns that didn't exist in any public jailbreak database&lt;/li&gt;
&lt;li&gt;Cross-provider differential testing revealed behavioral divergence invisible to single-target scanning&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The genetic approach doesn't just find known bypasses faster — it discovers novel ones that static testing would never reach.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;It's fully open source. One command to install:&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;basilisk-ai
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Point it at any LLM endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;basilisk scan &lt;span class="nt"&gt;--target&lt;/span&gt; https://your-llm-endpoint.com &lt;span class="nt"&gt;--mode&lt;/span&gt; standard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The full research paper is published with a permanent DOI:&lt;br&gt;
&lt;strong&gt;&lt;a href="https://doi.org/10.5281/zenodo.18909538" rel="noopener noreferrer"&gt;Basilisk: An Evolutionary AI Red-Teaming Framework for Systematic Security Evaluation of Large Language Models&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;GitHub: &lt;strong&gt;&lt;a href="https://github.com/regaan/basilisk" rel="noopener noreferrer"&gt;github.com/regaan/basilisk&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;I'm currently researching a new attack class called &lt;strong&gt;Prompt Cultivation&lt;/strong&gt; — a technique that doesn't use injection or commands at all. Instead, it exploits the model's own curiosity and reasoning to absorb it into a frame where safety guidelines become irrelevant. No override. No jailbreak. The model follows the idea, not the instruction.&lt;/p&gt;

&lt;p&gt;Paper coming soon.&lt;/p&gt;




&lt;p&gt;If you're building with LLMs, test them before attackers do. Your AI is already vulnerable. You just don't know it yet.&lt;/p&gt;

&lt;p&gt;Star the repo if this was useful: &lt;a href="https://github.com/regaan/basilisk" rel="noopener noreferrer"&gt;github.com/regaan/basilisk&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>opensource</category>
      <category>machinelearning</category>
    </item>
  </channel>
</rss>
