<?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: chataclaw</title>
    <description>The latest articles on DEV Community by chataclaw (@chataclaw).</description>
    <link>https://dev.to/chataclaw</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%2F3775364%2Fe04135c2-f925-438c-b0e2-682ac00bbc82.png</url>
      <title>DEV Community: chataclaw</title>
      <link>https://dev.to/chataclaw</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/chataclaw"/>
    <language>en</language>
    <item>
      <title>Put an AI Gatekeeper Behind nginx: Screen Attack Requests with vigilant-heron in 10 Minutes</title>
      <dc:creator>chataclaw</dc:creator>
      <pubDate>Tue, 27 Oct 2026 17:01:16 +0000</pubDate>
      <link>https://dev.to/chataclaw/vigilant-heron-a-rust-reverse-proxy-that-screens-web-requests-with-fixed-rules-and-ai-decision-5064</link>
      <guid>https://dev.to/chataclaw/vigilant-heron-a-rust-reverse-proxy-that-screens-web-requests-with-fixed-rules-and-ai-decision-5064</guid>
      <description>&lt;p&gt;Say this request arrives at your site:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /?id=1%20UNION%20SELECT%20password%20FROM%20users
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A signature WAF drops it on the &lt;code&gt;UNION SELECT&lt;/code&gt; rule. But attackers keep varying the shape, so a payload written differently slips past any rule set you maintain. Sending every request to an AI model has the opposite price tag: latency, cost, false positives, and shipping request bodies to an external API.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/coo-quack/vigilant-heron" rel="noopener noreferrer"&gt;vigilant-heron&lt;/a&gt; is a Rust reverse proxy built to sit between those two. Behind nginx or Cloudflare, it blocks obvious attacks with fixed rules and sends only the undecided remainder to a &lt;strong&gt;decision model&lt;/strong&gt; that returns attack scores. The request above gets this log line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"rule"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"rule_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"vh-sqli-001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"would_have"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"block"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rule &lt;code&gt;vh-sqli-001&lt;/code&gt; calls it an attack; shadow mode lets it through while recording that enforce mode would have answered 403. Getting here takes under ten minutes, and the barriers to trying it are low:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;On Linux, drop in a single binary. Docker and &lt;code&gt;cargo build&lt;/code&gt; work too.&lt;/li&gt;
&lt;li&gt;The configuration is about ten lines and looks like nginx.&lt;/li&gt;
&lt;li&gt;Shadow mode blocks nothing, so you can put it in front of a live site and just watch.&lt;/li&gt;
&lt;li&gt;The model is swappable: Jev, Clef, self-hosted Strands Decider, or your own HTTP endpoint.&lt;/li&gt;
&lt;li&gt;The whole evaluation run cost ¥6.63 in model API calls.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This article shows the overall design, a hands-on run from install to a blocked test attack, and then the design decisions: the evaluation order, what reaches the model, and what happens when the model cannot answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  The overall shape
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;client ──► nginx / Cloudflare ──► vigilant-heron ──► your site (upstream)
                                      │
                                      └──► decision model (Jev, Clef, Strands Decider, or your own)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Screening has two stages.&lt;br&gt;
The first stage is &lt;strong&gt;fixed rules&lt;/strong&gt;: known patterns such as SQL injection and path traversal are blocked without asking any model.&lt;br&gt;
The second stage is the decision model, which scores the requests the rules left undecided.&lt;br&gt;
The model is swappable: TypeSafe's Jev, Cloudflare Workers AI's Clef, a self-hosted Strands Decider, or an HTTP endpoint of your own.&lt;/p&gt;

&lt;p&gt;One principle governs the order: &lt;strong&gt;a rule's decision cannot be overturned by the model&lt;/strong&gt;.&lt;br&gt;
A request matched by a block rule never reaches the model, and no model answer can turn that block into an allow.&lt;br&gt;
In the other direction, plain requests for images, CSS and JavaScript skip the model entirely.&lt;br&gt;
Model calls happen only for the remainder that neither a rule nor a skip decided.&lt;/p&gt;

&lt;p&gt;The configuration is written in an nginx-like syntax: the listen address, per-site upstreams, the model choice, rate limits, and logging, all in one file.&lt;/p&gt;
&lt;h2&gt;
  
  
  Hands-on: from install to a blocked attack
&lt;/h2&gt;

&lt;p&gt;This follows the official &lt;a href="https://coo-quack.github.io/vigilant-heron/" rel="noopener noreferrer"&gt;Getting started&lt;/a&gt;. We will start a test upstream, run the router in shadow mode, send a SQL injection, and switch to enforce.&lt;/p&gt;
&lt;h3&gt;
  
  
  Install
&lt;/h3&gt;

&lt;p&gt;On Linux, download a release binary (x86_64 or aarch64, glibc 2.39 or newer):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;ARCH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;uname&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;   &lt;span class="c"&gt;# x86_64 or aarch64&lt;/span&gt;
curl &lt;span class="nt"&gt;-fsSLO&lt;/span&gt; &lt;span class="s2"&gt;"https://github.com/coo-quack/vigilant-heron/releases/latest/download/vigilant-heron-&lt;/span&gt;&lt;span class="nv"&gt;$ARCH&lt;/span&gt;&lt;span class="s2"&gt;-unknown-linux-gnu.tar.gz"&lt;/span&gt;
&lt;span class="nb"&gt;tar&lt;/span&gt; &lt;span class="nt"&gt;-xzf&lt;/span&gt; &lt;span class="s2"&gt;"vigilant-heron-&lt;/span&gt;&lt;span class="nv"&gt;$ARCH&lt;/span&gt;&lt;span class="s2"&gt;-unknown-linux-gnu.tar.gz"&lt;/span&gt;
&lt;span class="nb"&gt;sudo install&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; 755 &lt;span class="s2"&gt;"vigilant-heron-&lt;/span&gt;&lt;span class="nv"&gt;$ARCH&lt;/span&gt;&lt;span class="s2"&gt;-unknown-linux-gnu/vigilant-heron"&lt;/span&gt; /usr/local/bin/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On macOS, build from source. The toolchain named by &lt;code&gt;rust-toolchain.toml&lt;/code&gt; installs on the first build:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/coo-quack/vigilant-heron.git
&lt;span class="nb"&gt;cd &lt;/span&gt;vigilant-heron
cargo build &lt;span class="nt"&gt;--release&lt;/span&gt; &lt;span class="nt"&gt;--locked&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A Docker image is published as well:&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; ghcr.io/coo-quack/vigilant-heron:latest &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Start a test upstream
&lt;/h3&gt;

&lt;p&gt;Instead of a real application, serve a folder with Python's built-in web server. Keep it running in its own terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; site &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"hello from the upstream"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; site/index.html
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; http.server 3000 &lt;span class="nt"&gt;--bind&lt;/span&gt; 127.0.0.1 &lt;span class="nt"&gt;--directory&lt;/span&gt; site
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Write a minimal configuration
&lt;/h3&gt;

&lt;p&gt;Save this as &lt;code&gt;vigilant-heron.conf&lt;/code&gt;. It listens on port 8080 and forwards requests for &lt;code&gt;example.com&lt;/code&gt; to port 3000:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;listen&lt;/span&gt; &lt;span class="nf"&gt;127.0.0.1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;              &lt;span class="c1"&gt;# the address the router listens on&lt;/span&gt;
&lt;span class="k"&gt;pid&lt;/span&gt; &lt;span class="s"&gt;vigilant-heron.pid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;             &lt;span class="c1"&gt;# where the router writes its process id&lt;/span&gt;

&lt;span class="k"&gt;decision&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;backend&lt;/span&gt; &lt;span class="s"&gt;jev&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                    &lt;span class="c1"&gt;# the decision model; reads its key from TYPESAFE_API_KEY&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;site&lt;/span&gt; &lt;span class="s"&gt;example.com&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;upstream&lt;/span&gt; &lt;span class="s"&gt;http://127.0.0.1:3000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;# where allowed requests go&lt;/span&gt;
    &lt;span class="kn"&gt;mode&lt;/span&gt; &lt;span class="s"&gt;shadow&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                    &lt;span class="c1"&gt;# record decisions, block nothing&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While &lt;code&gt;mode shadow;&lt;/code&gt; is set, the router forwards every request and only records what it would have blocked.&lt;/p&gt;

&lt;h3&gt;
  
  
  Check the file and start the router
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;-t&lt;/code&gt; reads and validates the file, then exits. It starts nothing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;TYPESAFE_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;... vigilant-heron &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; vigilant-heron.conf
&lt;span class="go"&gt;configuration file vigilant-heron.conf test is successful
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start the router:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;TYPESAFE_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;... vigilant-heron &lt;span class="nt"&gt;-c&lt;/span&gt; vigilant-heron.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Send a normal request and a test attack
&lt;/h3&gt;

&lt;p&gt;From a second terminal, send an ordinary request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-i&lt;/span&gt; http://127.0.0.1:8080/ &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Host: example.com"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You get &lt;code&gt;200 OK&lt;/code&gt; with &lt;code&gt;hello from the upstream&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now send a request whose query string carries a SQL injection:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"http://127.0.0.1:8080/?id=1%20UNION%20SELECT%20password%20FROM%20users"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Host: example.com"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Shadow mode forwards this too, so the answer is still &lt;code&gt;200 OK&lt;/code&gt;. The router's terminal shows what it would have done:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"200"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"rule"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"rule_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"vh-sqli-001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"would_have"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"block"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;rule_id&lt;/code&gt; is the built-in rule that matched; &lt;code&gt;vh-sqli-001&lt;/code&gt; matches &lt;code&gt;UNION SELECT&lt;/code&gt; in a query string.&lt;br&gt;
&lt;code&gt;would_have&lt;/code&gt; is &lt;code&gt;block&lt;/code&gt;, which means enforce mode would have answered 403.&lt;br&gt;
&lt;code&gt;action&lt;/code&gt; is &lt;code&gt;allow&lt;/code&gt; because shadow mode forwarded the request.&lt;/p&gt;
&lt;h3&gt;
  
  
  Switch to enforce
&lt;/h3&gt;

&lt;p&gt;Read the shadow log until the would-be blocks are the ones you expect, change &lt;code&gt;mode shadow;&lt;/code&gt; to &lt;code&gt;mode enforce;&lt;/code&gt;, and signal the router to reload. The new file applies without a restart, and connections stay up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vigilant-heron &lt;span class="nt"&gt;-s&lt;/span&gt; reload &lt;span class="nt"&gt;-c&lt;/span&gt; vigilant-heron.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Send the test attack again. The response is now &lt;code&gt;403 Forbidden&lt;/code&gt;, and the upstream never sees the request. The ordinary request still returns &lt;code&gt;200 OK&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Put nginx in front
&lt;/h3&gt;

&lt;p&gt;In production, nginx stays in front, terminates TLS, and passes the WebSocket headers, the &lt;code&gt;Host&lt;/code&gt; the sites are matched on, and the client address:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;upstream&lt;/span&gt; &lt;span class="s"&gt;vigilant_heron&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;server&lt;/span&gt; &lt;span class="nf"&gt;127.0.0.1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;keepalive&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;443&lt;/span&gt; &lt;span class="s"&gt;ssl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;example.com&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;http://vigilant_heron&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_http_version&lt;/span&gt; &lt;span class="mf"&gt;1.1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                                &lt;span class="c1"&gt;# WebSocket upgrades need HTTP/1.1&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Host&lt;/span&gt; &lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                           &lt;span class="c1"&gt;# sites are matched on this header&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Real-IP&lt;/span&gt; &lt;span class="nv"&gt;$remote_addr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;               &lt;span class="c1"&gt;# the client address, read by real_ip_from&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Forwarded-For&lt;/span&gt; &lt;span class="nv"&gt;$proxy_add_x_forwarded_for&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Upgrade&lt;/span&gt; &lt;span class="nv"&gt;$http_upgrade&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Connection&lt;/span&gt; &lt;span class="nv"&gt;$connection_upgrade&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;map&lt;/span&gt; &lt;span class="nv"&gt;$http_upgrade&lt;/span&gt; &lt;span class="nv"&gt;$connection_upgrade&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;default&lt;/span&gt; &lt;span class="s"&gt;upgrade&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;''&lt;/span&gt;      &lt;span class="s"&gt;close&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add this to the router's configuration so the client address is trusted only from nginx:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;real_ip_from&lt;/span&gt; &lt;span class="mf"&gt;127.0&lt;/span&gt;&lt;span class="s"&gt;.0.1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;             &lt;span class="c1"&gt;# trust X-Real-IP only from nginx&lt;/span&gt;
&lt;span class="k"&gt;real_ip_header&lt;/span&gt; &lt;span class="s"&gt;X-Real-IP&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The evaluation order of each request
&lt;/h2&gt;

&lt;p&gt;Each request goes through these steps, and the first step that decides it ends the evaluation:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Rate and connection limits&lt;/strong&gt; (&lt;code&gt;limit_req&lt;/code&gt;, &lt;code&gt;limit_conn&lt;/code&gt;). A client over a limit is refused with the configured status (503 by default).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Host check.&lt;/strong&gt; A &lt;code&gt;Host&lt;/code&gt; header with characters that cannot appear in a host name gets 400.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Access list.&lt;/strong&gt; A denied client address gets 403.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Block rules.&lt;/strong&gt; The built-in rules that are on, then the top-level rules in file order, then the site's rules. The first match blocks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skip rules.&lt;/strong&gt; The first match forwards the request without calling the model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Static assets.&lt;/strong&gt; A plain GET or HEAD for an image, CSS or JavaScript path, with no query and no body, is forwarded without the model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decision model.&lt;/strong&gt; The remaining requests are scored, and the scores are compared with the site's thresholds.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Two properties are worth noting.&lt;/p&gt;

&lt;p&gt;First, block rules are always checked before skip rules, so a skip rule can never let through a request that a block rule matches.&lt;/p&gt;

&lt;p&gt;Second, the path is read in more than one way. Upstreams resolve the same bytes differently: Tomcat, Jetty and Spring drop &lt;code&gt;;&lt;/code&gt; path parameters, IIS and ASP.NET read &lt;code&gt;\&lt;/code&gt; as &lt;code&gt;/&lt;/code&gt;, and Express matches the raw path. vigilant-heron reads each path six ways (decoded, with &lt;code&gt;;&lt;/code&gt; cut, with &lt;code&gt;\&lt;/code&gt; as &lt;code&gt;/&lt;/code&gt;, combinations of these, and the raw path) and blocks when any reading matches. A static skip requires every reading to be static, so a path like &lt;code&gt;/index.php/x.css&lt;/code&gt; is always inspected. The request that reaches the upstream is the one the client sent, unchanged.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the model receives
&lt;/h2&gt;

&lt;p&gt;Before a copy of the request is sent to the model, credentials are removed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;cookie&lt;/code&gt;, &lt;code&gt;authorization&lt;/code&gt;, &lt;code&gt;proxy-authorization&lt;/code&gt;, &lt;code&gt;x-api-key&lt;/code&gt; and &lt;code&gt;x-auth-token&lt;/code&gt; are always dropped, and so is any other header whose name looks like a credential, such as &lt;code&gt;x-csrf-token&lt;/code&gt; or &lt;code&gt;x-session-id&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Form, JSON, multipart and XML bodies are scanned for fields whose names contain &lt;code&gt;password&lt;/code&gt;, &lt;code&gt;token&lt;/code&gt;, &lt;code&gt;secret&lt;/code&gt;, &lt;code&gt;credential&lt;/code&gt;, &lt;code&gt;card_number&lt;/code&gt; and similar parts, or a whole word like &lt;code&gt;pass&lt;/code&gt;, &lt;code&gt;pin&lt;/code&gt;, &lt;code&gt;otp&lt;/code&gt;, &lt;code&gt;session&lt;/code&gt; or &lt;code&gt;ssn&lt;/code&gt;. The values are replaced with &lt;code&gt;[REDACTED]&lt;/code&gt;. Names like &lt;code&gt;author&lt;/code&gt;, &lt;code&gt;shipping&lt;/code&gt; or &lt;code&gt;passenger&lt;/code&gt; are sent as they are.&lt;/li&gt;
&lt;li&gt;Only the first 8 KiB of the body is sent by default, cut on a UTF-8 boundary, with a flag when truncation happened.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All client data sits under &lt;code&gt;untrusted&lt;/code&gt;, telling the model to read it as data, not instructions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"event"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"untrusted"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"POST"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/login"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"query"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="s2"&gt;"user-agent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"…"&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"body"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user=alice&amp;amp;password=[REDACTED]"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"meta"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"host"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"client_ip"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"203.0.113.7"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"body_truncated"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model returns two scores between 0 and 1: &lt;code&gt;attack&lt;/code&gt; (is this an attack?) and &lt;code&gt;ai_instruction&lt;/code&gt; (does this request try to steer an AI or change how it is judged?):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"attack"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.02&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"ai_instruction"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each site compares the scores with two thresholds: &lt;code&gt;block_threshold&lt;/code&gt; (default 0.87) for &lt;code&gt;attack&lt;/code&gt; and &lt;code&gt;instruction_threshold&lt;/code&gt; (default 0.8) for &lt;code&gt;ai_instruction&lt;/code&gt;. A score at or above its threshold blocks the request.&lt;/p&gt;

&lt;p&gt;Four backends are available:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Jev&lt;/strong&gt; (default): TypeSafe's Jev at &lt;code&gt;api.typesafe.ai&lt;/code&gt;, with the key read from the &lt;code&gt;TYPESAFE_API_KEY&lt;/code&gt; environment variable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clef&lt;/strong&gt;: Cloudflare Workers AI, &lt;code&gt;clef-flash&lt;/code&gt; or &lt;code&gt;clef&lt;/code&gt;. Requires a URL with your account id and a token variable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strands Decider&lt;/strong&gt;: an Apache-2.0 decision model you run yourself. It serves &lt;code&gt;POST /v1/systemone&lt;/code&gt;, so the only required setting is the URL.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;http&lt;/strong&gt;: POSTs the JSON above to any endpoint you choose. Anything that answers &lt;code&gt;{"attack": &amp;lt;number&amp;gt;, "ai_instruction": &amp;lt;number&amp;gt;}&lt;/code&gt; works.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tokens are never written in the configuration file. &lt;code&gt;api_token_env&lt;/code&gt; names an environment variable, and the router reads it at startup.&lt;/p&gt;

&lt;p&gt;With third-party APIs (Jev, Clef), keep in mind that redaction is name-based and best-effort: a secret stored under a name without any of the listed words is sent as it is. Choose a backend you may disclose that data to, or run Strands Decider locally. The &lt;a href="https://coo-quack.github.io/vigilant-heron/security" rel="noopener noreferrer"&gt;Security page&lt;/a&gt; spells this out.&lt;/p&gt;

&lt;p&gt;Access logs contain the client address, method, path, status and decision fields — never a body, a query string, or a credential header. A log format that references a credential header via &lt;code&gt;$http_*&lt;/code&gt; is rejected when the configuration loads.&lt;/p&gt;

&lt;h2&gt;
  
  
  When the model cannot answer
&lt;/h2&gt;

&lt;p&gt;Models fail: timeouts, connection errors, 429s, unparseable answers. vigilant-heron groups the failures and lets you set the behavior per group:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;on_rate_limit&lt;/code&gt;&lt;/strong&gt;: the backend's HTTP 429, and saturation (no free concurrency slot within &lt;code&gt;queue_timeout&lt;/code&gt;). Defaults to allow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;on_client_busy&lt;/code&gt;&lt;/strong&gt;: the client already holds its share of in-flight model calls (&lt;code&gt;max_concurrency_per_client&lt;/code&gt;, default 8). Defaults to a 429 refusal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;on_decision_error&lt;/code&gt;&lt;/strong&gt;: timeouts, connection failures, malformed or out-of-range answers. Defaults to allow.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these touch rule decisions. A rule block stays a block whether the model is down, slow, or saturated.&lt;/p&gt;

&lt;p&gt;The allow defaults exist so that a provider outage does not block legitimate traffic. The trade-off is that an attacker who deliberately saturates the model gets requests past it. For paths that must stay safe, set &lt;code&gt;fail_closed_path&lt;/code&gt;: a failed decision on those prefixes is blocked whatever the settings say.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;site&lt;/span&gt; &lt;span class="s"&gt;example.com&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;upstream&lt;/span&gt; &lt;span class="s"&gt;http://app:8080&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;on_decision_error&lt;/span&gt; &lt;span class="s"&gt;block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;fail_closed_path&lt;/span&gt; &lt;span class="n"&gt;/wp-login.php&lt;/span&gt; &lt;span class="n"&gt;/wp-admin&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, decision errors normally block, rate limits allow, and the login paths block on any failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Built-in rules
&lt;/h2&gt;

&lt;p&gt;Twenty-four built-in rules ship in the binary and are on by default. The categories are SQL injection, XSS, path traversal, command injection, file inclusion, SSRF, template and expression injection, XXE, and known scanner user agents. For example, &lt;code&gt;vh-sqli-001&lt;/code&gt; matches &lt;code&gt;UNION SELECT&lt;/code&gt; in a query string, &lt;code&gt;vh-ssrf-001&lt;/code&gt; matches the cloud metadata address &lt;code&gt;169.254.169.254&lt;/code&gt;, and &lt;code&gt;vh-expr-001&lt;/code&gt; matches the Log4Shell form &lt;code&gt;${jndi:&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The rules are written to miss an attack rather than block ordinary traffic. Patterns that could appear in a code paste or a post about SQL or HTML are deliberately left out; such requests go to the decision model instead. As a consequence, most rules inspect query strings. In the measurement, the built-in rules blocked no allowed request on either split.&lt;/p&gt;

&lt;p&gt;Opt-in virtual patches for known CVEs ship off by default. For instance, &lt;code&gt;vh-cve-2026-9103&lt;/code&gt; and &lt;code&gt;vh-cve-2026-8481&lt;/code&gt; cover IBM Langflow OSS and are enabled per site with &lt;code&gt;enable_rule&lt;/code&gt; where that product runs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measurements
&lt;/h2&gt;

&lt;p&gt;The models and rules were measured on a labelled dataset. It is synthetic and small — about 85 attacks and 110 allowed requests per split. Thresholds were chosen on the dev split, so only the holdout figures are out of sample. Treat the numbers as a starting point for shadow mode on your own traffic, not as a guarantee.&lt;/p&gt;

&lt;p&gt;With the &lt;code&gt;attack&lt;/code&gt; threshold chosen on dev (the highest recall at a dev false-positive rate of 2% or less):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Backend&lt;/th&gt;
&lt;th&gt;Threshold&lt;/th&gt;
&lt;th&gt;Dev recall / FPR&lt;/th&gt;
&lt;th&gt;Holdout recall / FPR&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;jev-latest&lt;/td&gt;
&lt;td&gt;0.87&lt;/td&gt;
&lt;td&gt;0.871 / 0.018&lt;/td&gt;
&lt;td&gt;0.892 / 0.009&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;clef-flash&lt;/td&gt;
&lt;td&gt;0.88&lt;/td&gt;
&lt;td&gt;0.694 / 0.018&lt;/td&gt;
&lt;td&gt;0.723 / 0.000&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;On the &lt;code&gt;ai_instruction&lt;/code&gt; side, a threshold of 0.8 blocked no allowed request and still blocked all 15 prompt-injection rows on both splits.&lt;/p&gt;

&lt;p&gt;Combined with the built-in rules, Jev showed zero false positives and gained recall in every split. The models' weak spots also showed: both miss most scanner probes like &lt;code&gt;/.env&lt;/code&gt; and &lt;code&gt;/.git/config&lt;/code&gt;, which deterministic &lt;code&gt;deny_path&lt;/code&gt; entries handle better.&lt;/p&gt;

&lt;p&gt;Latency, measured from a home connection in Japan through the router: a decision p50 of about 146 ms for Jev and 375 ms for clef-flash. 82% of clef-flash calls exceeded the default 300 ms timeout, so about 1000 ms is recommended for Clef. Strands Decider on a single NVIDIA L4 measured a p50 of 99 ms and a p95 of 436 ms for the 2B checkpoint&lt;sup id="fnref1"&gt;1&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;A load test with a local stub backend (20 ms delay, Apple M2, 8 logical CPUs) reached 40.9 req/s at concurrency 1, 567.9 req/s at 16, and 2,525.8 req/s at 64 with a p95 of 27.7 ms.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it cannot do
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Credential stuffing.&lt;/strong&gt; A client trying a hundred passwords never shows in a single-request verdict. Throttling is the job of &lt;code&gt;limit_req&lt;/code&gt; on the path.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Triple encoding.&lt;/strong&gt; Paths and queries are decoded at most twice. A payload encoded three times is not seen as the attack it encodes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Beyond the inspected prefix.&lt;/strong&gt; Both rules and models see only the first &lt;code&gt;inspect_body&lt;/code&gt; bytes of a body.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WebSocket after the handshake.&lt;/strong&gt; The upgrade is screened like any request; after a 101, frames are copied through without inspection.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Everything inside multipart.&lt;/strong&gt; Multipart redaction is lexical, so a sensitive value can still reach the model in some layouts.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;vigilant-heron blocks what fixed rules can block deterministically, and scores the rest with a decision model. Rule decisions cannot be overturned, model failures are configured per group, and static files never reach the model. Deployment starts in shadow mode, and one &lt;code&gt;-s reload&lt;/code&gt; switches a site to enforce.&lt;/p&gt;

&lt;p&gt;If this sounds useful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/coo-quack/vigilant-heron" rel="noopener noreferrer"&gt;Repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://coo-quack.github.io/vigilant-heron/" rel="noopener noreferrer"&gt;Documentation&lt;/a&gt; — &lt;a href="https://coo-quack.github.io/vigilant-heron/getting-started" rel="noopener noreferrer"&gt;Getting started&lt;/a&gt;, &lt;a href="https://coo-quack.github.io/vigilant-heron/how-it-works" rel="noopener noreferrer"&gt;How it works&lt;/a&gt;, &lt;a href="https://coo-quack.github.io/vigilant-heron/decision-backends" rel="noopener noreferrer"&gt;Decision backends&lt;/a&gt;, &lt;a href="https://coo-quack.github.io/vigilant-heron/evaluation" rel="noopener noreferrer"&gt;Evaluation&lt;/a&gt;, &lt;a href="https://coo-quack.github.io/vigilant-heron/security" rel="noopener noreferrer"&gt;Security&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;The Strands Decider figures come from the JevBench tasks, not from this router's questions. Also note that its server has no authentication (keep it on localhost or a private network), and the published checkpoints use a 4,096-token window that silently cuts long prompts at the end.&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>rust</category>
      <category>security</category>
      <category>nginx</category>
      <category>ai</category>
    </item>
    <item>
      <title>Talk to Your AI — Meet Talkative Lobster</title>
      <dc:creator>chataclaw</dc:creator>
      <pubDate>Thu, 12 Mar 2026 12:09:16 +0000</pubDate>
      <link>https://dev.to/chataclaw/talk-to-your-ai-meet-talkative-lobster-1f24</link>
      <guid>https://dev.to/chataclaw/talk-to-your-ai-meet-talkative-lobster-1f24</guid>
      <description>&lt;p&gt;Have you ever wanted to just &lt;em&gt;talk&lt;/em&gt; to your AI assistant? Like, literally speak into your microphone and hear it respond naturally?&lt;/p&gt;

&lt;p&gt;I work with AI all day, and sometimes typing feels... slow. Copy-pasting is tedious. Sometimes I'm cooking, walking, or just tired of staring at a screen. I wanted something that felt like a real conversation.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;Talkative Lobster&lt;/strong&gt; — a desktop app that turns your voice into AI conversation and back again.&lt;/p&gt;




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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You speak  →  Speech-to-Text  →  LLM  →  Text-to-Speech  →  You hear
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You talk into your mic, the AI thinks, and then speaks back to you. No typing, no copy-pasting — just natural back-and-forth conversation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key features:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Interrupt anytime&lt;/strong&gt; — Just start talking to cut in mid-response, like a real conversation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Works offline&lt;/strong&gt; — Local whisper.cpp and Piper TTS when you need privacy or no network&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Japanese support&lt;/strong&gt; — VOICEVOX and Kokoro voices, plus "aizuchi" (those little "mm-hmm" sounds)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multiple providers&lt;/strong&gt; — ElevenLabs, OpenAI, or local options for both STT and TTS&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Privacy-first&lt;/strong&gt; — API keys encrypted on your machine, everything routes through your own gateway&lt;/li&gt;
&lt;/ul&gt;




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

&lt;h3&gt;
  
  
  Voice Detection
&lt;/h3&gt;

&lt;p&gt;The app uses &lt;strong&gt;Silero VAD&lt;/strong&gt; (Voice Activity Detection) — a neural network trained on real speech patterns. This means it knows when you're actually talking versus when there's background noise, music, or keyboard clicks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Speech-to-Text Options
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ElevenLabs Scribe&lt;/strong&gt; — Best accuracy, fastest&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OpenAI Whisper&lt;/strong&gt; — Reliable, widely used&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;whisper.cpp&lt;/strong&gt; — Runs locally, no network needed&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Text-to-Speech Options
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ElevenLabs&lt;/strong&gt; — Most natural voices&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;VOICEVOX&lt;/strong&gt; — Great for Japanese&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kokoro&lt;/strong&gt; — Japanese + English&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Piper&lt;/strong&gt; — Lightweight, runs offline&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  LLM (OpenClaw Gateway)
&lt;/h3&gt;

&lt;p&gt;The AI part runs through &lt;strong&gt;&lt;a href="https://github.com/coo-quack/openclaw" rel="noopener noreferrer"&gt;OpenClaw&lt;/a&gt;&lt;/strong&gt; — a local gateway that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lets you switch models without code changes&lt;/li&gt;
&lt;li&gt;Handles rate limits gracefully&lt;/li&gt;
&lt;li&gt;Keeps all your API keys in one place&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Challenges I Faced
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Challenge 1: The Echo Problem
&lt;/h3&gt;

&lt;p&gt;At first, the AI's voice was triggering the microphone, creating infinite loops. 😅&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; A speaker monitor that "subtracts" the audio output from the input stream. Now only your voice triggers the AI.&lt;/p&gt;

&lt;h3&gt;
  
  
  Challenge 2: Latency
&lt;/h3&gt;

&lt;p&gt;3-4 second delays made conversations feel robotic and awkward.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stream responses — start speaking as soon as the first tokens arrive&lt;/li&gt;
&lt;li&gt;Optimized audio buffers — balance latency vs. quality&lt;/li&gt;
&lt;li&gt;Added "aizuchi" sounds — those little "mm-hmm" fillers during thinking time&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Challenge 3: Japanese Support
&lt;/h3&gt;

&lt;p&gt;Most TTS voices sound robotic in Japanese. And Japanese conversations have those subtle "aizuchi" sounds that feel unnatural without them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; Integrated VOICEVOX and Kokoro for natural prosody, and added optional aizuchi sounds during thinking.&lt;/p&gt;




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

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Node.js 20+&lt;/li&gt;
&lt;li&gt;pnpm&lt;/li&gt;
&lt;li&gt;An &lt;a href="https://github.com/coo-quack/openclaw" rel="noopener noreferrer"&gt;OpenClaw&lt;/a&gt; gateway running locally&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Quick Start
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/coo-quack/talkative-lobster.git
&lt;span class="nb"&gt;cd &lt;/span&gt;talkative-lobster
pnpm &lt;span class="nb"&gt;install
&lt;/span&gt;pnpm dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On first launch, the Settings modal walks you through connecting your gateway and choosing your preferred STT/TTS providers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Downloads
&lt;/h3&gt;

&lt;p&gt;Pre-built binaries available:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Platform&lt;/th&gt;
&lt;th&gt;Link&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;macOS (Apple Silicon)&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/coo-quack/talkative-lobster/releases/latest/download/talkative-lobster-arm64.dmg" rel="noopener noreferrer"&gt;DMG&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;macOS (Intel)&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/coo-quack/talkative-lobster/releases/latest/download/talkative-lobster-x64.dmg" rel="noopener noreferrer"&gt;DMG&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Windows&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/coo-quack/talkative-lobster/releases/latest/download/talkative-lobster-x64-setup.exe" rel="noopener noreferrer"&gt;EXE&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Linux&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/coo-quack/talkative-lobster/releases/latest/download/talkative-lobster-x86_64.AppImage" rel="noopener noreferrer"&gt;AppImage&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




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

&lt;ul&gt;
&lt;li&gt;More TTS providers (Azure, Google Cloud)&lt;/li&gt;
&lt;li&gt;Voice presets for different use cases&lt;/li&gt;
&lt;li&gt;Conversation history with search&lt;/li&gt;
&lt;li&gt;Custom wake words&lt;/li&gt;
&lt;li&gt;Mobile companion app&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Voice interfaces are finally good enough to be useful — not just gimmicky. The combination of accurate speech recognition, fast LLMs, and natural text-to-speech creates something that feels genuinely conversational.&lt;/p&gt;

&lt;p&gt;If you've ever wanted to just &lt;em&gt;talk&lt;/em&gt; to your AI while doing something else — cooking, walking, debugging at 2 AM — give Talkative Lobster a try.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Links:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;📂 &lt;a href="https://github.com/coo-quack/talkative-lobster" rel="noopener noreferrer"&gt;GitHub Repository&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📖 &lt;a href="https://coo-quack.github.io/talkative-lobster/" rel="noopener noreferrer"&gt;Documentation&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Built with 🦞 by the coo-quack team&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>voice</category>
      <category>showdev</category>
      <category>openclaw</category>
    </item>
    <item>
      <title>Stop Claude Code from leaking your secrets — introducing sensitive-canary</title>
      <dc:creator>chataclaw</dc:creator>
      <pubDate>Mon, 23 Feb 2026 11:46:45 +0000</pubDate>
      <link>https://dev.to/chataclaw/stop-claude-code-from-leaking-your-secrets-introducing-sensitive-canary-826</link>
      <guid>https://dev.to/chataclaw/stop-claude-code-from-leaking-your-secrets-introducing-sensitive-canary-826</guid>
      <description>&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;Claude Code is powerful. That's exactly what makes it risky.&lt;/p&gt;

&lt;p&gt;Without realizing it, you might:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ask Claude to "take a look at" your &lt;code&gt;.env&lt;/code&gt; file&lt;/li&gt;
&lt;li&gt;Paste an AWS key into the terminal for a quick sanity check&lt;/li&gt;
&lt;li&gt;Let Claude run &lt;code&gt;cat .env.production&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of these send your secrets and PII straight to the Anthropic API. Once they leave your machine, they're out of your hands.&lt;/p&gt;

&lt;p&gt;And here's the part people miss: &lt;strong&gt;you don't have to type secrets into the prompt for them to leak&lt;/strong&gt;. Claude Code autonomously reads files as context. It might pick up your &lt;code&gt;.env&lt;/code&gt; without you asking. That's a vector most developers don't think about.&lt;/p&gt;

&lt;h2&gt;
  
  
  What sensitive-canary does
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/coo-quack/sensitive-canary" rel="noopener noreferrer"&gt;sensitive-canary&lt;/a&gt; is a zero-config, fully local Claude Code plugin that intercepts secrets and PII before they reach the API. No external servers. No configuration. No API keys.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cat .env                     → blocked ✅
Paste AWS key in prompt      → blocked ✅
echo $API_KEY                → blocked ✅ (env var value scanned)
Claude autonomously reads .env → blocked ✅
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When a block triggers, you see exactly what was caught:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;🐦 sensitive-canary: sensitive data detected — blocked

  [Secret] AWS Access Key ID (aws-access-key): AKIA****MPLE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Zero-config. Fully local.
&lt;/h2&gt;

&lt;p&gt;sensitive-canary runs entirely on your machine. No data leaves to a third-party service. Detection and blocking happen locally — the plugin itself never phones home.&lt;/p&gt;

&lt;p&gt;Install it in two commands and it starts protecting you immediately. No config files, no environment variables, no restart required.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two hooks, two layers of protection
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;UserPromptSubmit hook&lt;/strong&gt; — runs before every prompt is sent to the API. If your message contains secrets or PII, it's blocked before Claude ever sees it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PreToolUse hook&lt;/strong&gt; — runs before Claude uses the &lt;code&gt;Read&lt;/code&gt; or &lt;code&gt;Bash&lt;/code&gt; tool. &lt;strong&gt;This is the critical one.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Even if you're careful about what you type, Claude proactively reads files it thinks are relevant. This hook stops that at the source:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Blocks &lt;code&gt;.env&lt;/code&gt; and &lt;code&gt;.env.*&lt;/code&gt; files unconditionally by filename (before contents are ever read)&lt;/li&gt;
&lt;li&gt;Scans file contents before Claude reads them&lt;/li&gt;
&lt;li&gt;Catches inline secrets in Bash commands (&lt;code&gt;echo AKIAIOSFODNN7EXAMPLE&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Looks up env vars referenced in commands and checks their actual values&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What gets detected
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;29 secret types&lt;/strong&gt;, sourced from &lt;a href="https://github.com/gitleaks/gitleaks" rel="noopener noreferrer"&gt;gitleaks&lt;/a&gt; and &lt;a href="https://github.com/trufflesecurity/trufflehog" rel="noopener noreferrer"&gt;TruffleHog&lt;/a&gt; detector definitions:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Examples&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cloud&lt;/td&gt;
&lt;td&gt;AWS Access Key ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Source control&lt;/td&gt;
&lt;td&gt;GitHub PAT, GitLab PAT, GitHub fine-grained token&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI services&lt;/td&gt;
&lt;td&gt;Anthropic API key, OpenAI API key / project key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Communication&lt;/td&gt;
&lt;td&gt;Slack token, Discord webhook, Telegram bot token&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Payment&lt;/td&gt;
&lt;td&gt;Stripe secret/restricted key, credit cards (Luhn-validated)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Email services&lt;/td&gt;
&lt;td&gt;SendGrid, Mailgun, Mailchimp&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auth&lt;/td&gt;
&lt;td&gt;JWT, PEM private keys, database connection strings&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;PII detection:&lt;/strong&gt; email addresses, US SSNs, US/JP phone numbers, Japanese postal codes, private IPv4 addresses.&lt;/p&gt;

&lt;p&gt;Entropy filtering reduces false positives — &lt;code&gt;API_KEY=placeholder&lt;/code&gt; won't trigger a block.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install in two commands
&lt;/h2&gt;

&lt;p&gt;Inside a Claude Code session:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/plugin marketplace add coo-quack/sensitive-canary
/plugin install sensitive-canary@coo-quack
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No restart, no config files, no API keys. Done.&lt;/p&gt;

&lt;h2&gt;
  
  
  When you need to share secrets intentionally
&lt;/h2&gt;

&lt;p&gt;Add a tag to your prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[allow-secret] Please review my .env.example — these are sample values
[allow-pii] Here's the user data schema I'm working with
[allow-all] Sanity-check this with test credentials
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tags apply only to the message they appear in. They don't carry over to the next turn.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Without sensitive-canary&lt;/th&gt;
&lt;th&gt;With sensitive-canary&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;cat .env&lt;/code&gt; → full contents sent to Claude ❌&lt;/td&gt;
&lt;td&gt;Blocked by filename before Claude reads it ✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Paste &lt;code&gt;AKIAIOSFODNN7EXAMPLE&lt;/code&gt; in prompt ❌&lt;/td&gt;
&lt;td&gt;Blocked before the API call ✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;echo $API_KEY&lt;/code&gt; with live key ❌&lt;/td&gt;
&lt;td&gt;Env var value scanned and blocked ✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude autonomously reads &lt;code&gt;.env&lt;/code&gt; as context ❌&lt;/td&gt;
&lt;td&gt;Blocked at the PreToolUse hook ✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Requires setup and external accounts ❌&lt;/td&gt;
&lt;td&gt;Zero-config, fully local, no external calls ✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If you use Claude Code seriously, sensitive-canary is the seatbelt you didn't know you needed.&lt;/p&gt;

&lt;p&gt;🔗 Docs: &lt;a href="https://coo-quack.github.io/sensitive-canary/" rel="noopener noreferrer"&gt;https://coo-quack.github.io/sensitive-canary/&lt;/a&gt;&lt;br&gt;
🐙 GitHub: &lt;a href="https://github.com/coo-quack/sensitive-canary" rel="noopener noreferrer"&gt;https://github.com/coo-quack/sensitive-canary&lt;/a&gt;&lt;/p&gt;

</description>
      <category>security</category>
    </item>
    <item>
      <title>I Built an MCP Server to Fix 21 Things LLMs Are Bad At</title>
      <dc:creator>chataclaw</dc:creator>
      <pubDate>Mon, 16 Feb 2026 09:39:52 +0000</pubDate>
      <link>https://dev.to/chataclaw/i-built-an-mcp-server-to-fix-21-things-llms-are-bad-at-4c9l</link>
      <guid>https://dev.to/chataclaw/i-built-an-mcp-server-to-fix-21-things-llms-are-bad-at-4c9l</guid>
      <description>

&lt;p&gt;title: "I Built an MCP Server to Fix 21 Things LLMs Are Bad At"&lt;br&gt;
published: true&lt;br&gt;
tags: ai, mcp, opensource, tools&lt;/p&gt;
&lt;h2&gt;
  
  
  cover_image: &lt;a href="https://i.imgur.com/la0C54P.png" rel="noopener noreferrer"&gt;https://i.imgur.com/la0C54P.png&lt;/a&gt;
&lt;/h2&gt;



&lt;p&gt;title: "I Built an MCP Server to Fix 21 Things LLMs Are Bad At"&lt;br&gt;
published: true&lt;/p&gt;
&lt;h2&gt;
  
  
  tags: ai, mcp, opensource, tools
&lt;/h2&gt;
&lt;h2&gt;
  
  
  The Problem: When AI Hallucinates the Basics
&lt;/h2&gt;

&lt;p&gt;Picture this: You're vibe coding with Claude, building something cool. You ask it to calculate &lt;code&gt;2^128&lt;/code&gt; and it confidently tells you it's "340282366920938463463374607431768211456" — except it's wrong. You ask for a SHA-256 hash and get something that looks plausible but is completely made up. You need a UUID and receive &lt;code&gt;12345678-1234-1234-1234-123456789abc&lt;/code&gt; (which, let's be honest, is faker than your enthusiasm at 9 AM meetings).&lt;/p&gt;

&lt;p&gt;LLMs are incredible at reasoning, writing, and creative tasks. But ask them to count characters, generate cryptographically secure random numbers, or calculate date differences accurately? Hallucination city.&lt;/p&gt;

&lt;p&gt;Here's the thing: &lt;strong&gt;individual tools exist&lt;/strong&gt; for some of these problems. But when you're in flow, switching context to grab a calculator, run &lt;code&gt;date&lt;/code&gt; commands, or fire up an encoding tool kills momentum. I wanted something comprehensive that just &lt;em&gt;works&lt;/em&gt; — automatically, locally, without thinking about it.&lt;/p&gt;

&lt;p&gt;Hi, I'm Chataclaw (&lt;a href="https://github.com/chataclaw" rel="noopener noreferrer"&gt;@chataclaw&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;Calc MCP&lt;/strong&gt;: an MCP server with 21 tools for all the deterministic stuff AI struggles with.&lt;/p&gt;
&lt;h2&gt;
  
  
  What is Calc MCP?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/coo-quack/calc-mcp" rel="noopener noreferrer"&gt;Calc MCP&lt;/a&gt; is a Model Context Protocol server that gives your AI assistant 21 specialized tools for precise, deterministic operations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🧮 &lt;strong&gt;Math evaluation&lt;/strong&gt; with sandboxed execution&lt;/li&gt;
&lt;li&gt;🎲 &lt;strong&gt;Cryptographic randomness&lt;/strong&gt; (real UUIDs, secure tokens)&lt;/li&gt;
&lt;li&gt;📅 &lt;strong&gt;Date/time arithmetic&lt;/strong&gt; that doesn't hallucinate&lt;/li&gt;
&lt;li&gt;🔐 &lt;strong&gt;Hashing &amp;amp; encoding&lt;/strong&gt; (base64, hex, URL, JWT, and more)&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Validation &amp;amp; parsing&lt;/strong&gt; (JSON, regex, cron, Luhn, IP, semver)&lt;/li&gt;
&lt;li&gt;🎨 &lt;strong&gt;Conversion utilities&lt;/strong&gt; (colors, units, character info)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Why You Should Care
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;🔒 Secure by Design&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sandboxed math evaluation (no arbitrary code execution)&lt;/li&gt;
&lt;li&gt;ReDoS protection for regex operations&lt;/li&gt;
&lt;li&gt;Weak hash warnings (MD5/SHA-1 deprecation notices)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;💰 Completely Free&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No API keys required&lt;/li&gt;
&lt;li&gt;Runs 100% locally&lt;/li&gt;
&lt;li&gt;Works offline&lt;/li&gt;
&lt;li&gt;MIT License&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Before &amp;amp; After
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Without Calc MCP:&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;You: "What's the SHA-256 hash of 'hello'?"
AI: "The hash is 2cf24dba5fb0a3... (hallucinates rest)"

You: "Generate a v4 UUID"
AI: "12345678-1234-5678-1234-567812345678"
     ❌ Not random, not cryptographically secure

You: "How many days until Christmas 2026?"
AI: "Let me calculate... approximately 280 days"
     ❌ Wrong, and "approximately" isn't good enough
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;With Calc MCP:&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;You: "What's the SHA-256 hash of 'hello'?"
AI: Uses hash tool → Returns actual SHA-256
     ✅ "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"

You: "Generate a v4 UUID"
AI: Uses random tool → Returns crypto.randomUUID()
     ✅ "f47ac10b-58cc-4372-a567-0e02b2c3d479"

You: "How many days until Christmas 2026?"
AI: Uses date tool → Precise calculation
     ✅ "313 days" (accurate to the second)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Quick Start
&lt;/h2&gt;

&lt;p&gt;Install via npm:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @coo-quack/calc-mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then add to your MCP client configuration:&lt;/p&gt;

&lt;h3&gt;
  
  
  Claude Desktop
&lt;/h3&gt;

&lt;p&gt;Edit &lt;code&gt;~/Library/Application Support/Claude/claude_desktop_config.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"calc"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"calc-mcp"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Claude Code (openclaw)
&lt;/h3&gt;

&lt;p&gt;In your workspace:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mcporter &lt;span class="nb"&gt;install &lt;/span&gt;calc-mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Cursor / VS Code
&lt;/h3&gt;

&lt;p&gt;Add to your MCP settings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"calc"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@coo-quack/calc-mcp"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Windsurf
&lt;/h3&gt;

&lt;p&gt;Similar to VS Code — add the server to your MCP configuration file.&lt;/p&gt;

&lt;p&gt;Restart your client, and you're ready to go. Full docs at &lt;a href="https://coo-quack.github.io/calc-mcp/" rel="noopener noreferrer"&gt;coo-quack.github.io/calc-mcp&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 21 Tools: A Showcase
&lt;/h2&gt;

&lt;p&gt;Let me walk you through what's inside, organized by category.&lt;/p&gt;

&lt;h3&gt;
  
  
  🧮 Math &amp;amp; Calculation
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;math&lt;/code&gt;&lt;/strong&gt; — Evaluate mathematical expressions safely&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Expression: "2^128 + sqrt(144)"
Result: 340282366920938463463374607431768211468
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Supports standard operators, functions (sin, cos, log, sqrt, etc.), constants (PI, E), and complex expressions. Sandboxed execution means no &lt;code&gt;eval()&lt;/code&gt; risks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;count&lt;/code&gt;&lt;/strong&gt; — Accurately count characters, words, lines, bytes&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Text: "Hello 世界"
Characters: 8
Bytes (UTF-8): 12
Words: 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Perfect for when you need exact counts for APIs with character limits.&lt;/p&gt;

&lt;h3&gt;
  
  
  📅 Date &amp;amp; Time
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;datetime&lt;/code&gt;&lt;/strong&gt; — Current date/time in any format or timezone&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Format: "YYYY-MM-DD HH:mm:ss"
Timezone: "America/New_York"
Result: "2026-02-16 01:05:00"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;date&lt;/code&gt;&lt;/strong&gt; — Date arithmetic and difference calculations&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Add 45 days to 2026-02-16: 2026-04-02
Days between 2026-01-01 and 2026-12-25: 358
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No more "roughly X days" hallucinations.&lt;/p&gt;

&lt;h3&gt;
  
  
  🎲 Cryptographic Randomness
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;random&lt;/code&gt;&lt;/strong&gt; — Generate secure random values&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;UUIDs (v4, v7)&lt;/li&gt;
&lt;li&gt;Cryptographically secure integers&lt;/li&gt;
&lt;li&gt;Random strings (alphanumeric, hex, base64)&lt;/li&gt;
&lt;li&gt;Secure tokens
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;UUID v4: "f81d4fae-7dec-11d0-a765-00a0c91e6bf6"
Random int (1-100): 73
Secure token (32 bytes): "a7f3c9..."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  🔐 Hashing &amp;amp; Encoding
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;hash&lt;/code&gt;&lt;/strong&gt; — Generate cryptographic hashes&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MD5, SHA-1, SHA-256, SHA-384, SHA-512
Input: "password123"
SHA-256: "ef92b778bafe771e89245b89ecbc08a44a4e166c06659911881f383d4473e94f"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Includes warnings when using weak algorithms (MD5/SHA-1).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;base64&lt;/code&gt;&lt;/strong&gt; — Encode/decode base64&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Encode: "Hello" → "SGVsbG8="
Decode: "SGVsbG8=" → "Hello"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;encode&lt;/code&gt;&lt;/strong&gt; — URL and HTML encoding/decoding&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;URL encode: "hello world" → "hello%20world"
HTML encode: "&amp;lt;div&amp;gt;" → "&amp;amp;lt;div&amp;amp;gt;"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ✅ Validation &amp;amp; Parsing
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;json_validate&lt;/code&gt;&lt;/strong&gt; — Validate and pretty-print JSON&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Input: '{"name":"Alice","age":30}'
Valid: ✅
Pretty output with indentation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;regex&lt;/code&gt;&lt;/strong&gt; — Test regex patterns with ReDoS protection&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Pattern: "^\d{3}-\d{4}$"
Test: "123-4567"
Match: ✅
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;cron_parse&lt;/code&gt;&lt;/strong&gt; — Parse cron expressions to human-readable format&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Expression: "0 9 * * 1-5"
Readable: "At 09:00 AM, Monday through Friday"
Next 5 runs calculated
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;luhn&lt;/code&gt;&lt;/strong&gt; — Validate credit card numbers, generate check digits&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Number: "4532015112830366"
Valid: ✅ (Luhn algorithm check)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;ip&lt;/code&gt;&lt;/strong&gt; — Validate and analyze IP addresses&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;IP: "192.168.1.1"
Version: IPv4
Private: ✅
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;semver&lt;/code&gt;&lt;/strong&gt; — Parse and compare semantic versions&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Version: "1.8.0"
Major: 1, Minor: 8, Patch: 0
Compare: "1.8.0" &amp;gt; "1.7.5" → true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  🎨 Conversion Utilities
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;base&lt;/code&gt;&lt;/strong&gt; — Convert between number bases (2-36)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Decimal 255 → Hex: "FF"
Binary "1111" → Decimal: 15
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;diff&lt;/code&gt;&lt;/strong&gt; — Character and word-level text diffs&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Text1: "The quick brown fox"
Text2: "The slow brown dog"
Changes highlighted with additions/deletions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;color&lt;/code&gt;&lt;/strong&gt; — Convert between color formats&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hex "#FF5733" → RGB: "rgb(255, 87, 51)"
RGB → HSL: "hsl(14, 100%, 60%)"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;convert&lt;/code&gt;&lt;/strong&gt; — Unit conversions (length, weight, temperature, etc.)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;100 cm → 39.37 inches
32°F → 0°C
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;char_info&lt;/code&gt;&lt;/strong&gt; — Character information and Unicode details&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Character: "€"
Unicode: U+20AC
Category: Currency Symbol
Name: EURO SIGN
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;jwt_decode&lt;/code&gt;&lt;/strong&gt; — Decode JWT tokens (header + payload)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Token: "eyJhbGciOiJIUzI1..."
Header: {"alg": "HS256", "typ": "JWT"}
Payload: {"sub": "1234", "name": "John"}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;url_parse&lt;/code&gt;&lt;/strong&gt; — Parse URLs into components&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;URL: "https://example.com/path?q=search#section"
Protocol: https
Host: example.com
Path: /path
Query: q=search
Hash: #section
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Security Design
&lt;/h2&gt;

&lt;p&gt;I didn't just slap together a bunch of &lt;code&gt;eval()&lt;/code&gt; calls and call it a day. Security matters:&lt;/p&gt;

&lt;h3&gt;
  
  
  🔒 Sandboxed Math Evaluation
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;math&lt;/code&gt; tool uses a safe expression parser, not JavaScript's &lt;code&gt;eval()&lt;/code&gt;. You can't execute arbitrary code like &lt;code&gt;process.exit()&lt;/code&gt; or &lt;code&gt;require('fs')&lt;/code&gt;. It evaluates mathematical expressions in a controlled environment.&lt;/p&gt;

&lt;h3&gt;
  
  
  🛡️ ReDoS Protection
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;regex&lt;/code&gt; tool includes timeouts and complexity checks to prevent Regular Expression Denial of Service attacks. No hanging your AI session with catastrophic backtracking patterns.&lt;/p&gt;

&lt;h3&gt;
  
  
  ⚠️ Weak Hash Warnings
&lt;/h3&gt;

&lt;p&gt;Using MD5 or SHA-1 for hashing? The tool works but warns you that these algorithms are cryptographically broken and shouldn't be used for security-sensitive applications. Gentle nudge toward SHA-256+.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;When you're coding with AI, context-switching kills flow. Having these tools available &lt;strong&gt;automatically&lt;/strong&gt; means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No more "let me open a calculator"&lt;/li&gt;
&lt;li&gt;No more "hold on, lemme check that date"&lt;/li&gt;
&lt;li&gt;No more copy-pasting to encoding websites&lt;/li&gt;
&lt;li&gt;No more trusting hallucinated hashes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your AI assistant becomes genuinely reliable for the precise, deterministic stuff it used to struggle with.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get Started
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;📦 Install: &lt;code&gt;npm install -g @coo-quack/calc-mcp&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;📖 Docs: &lt;a href="https://coo-quack.github.io/calc-mcp/" rel="noopener noreferrer"&gt;coo-quack.github.io/calc-mcp&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💻 GitHub: &lt;a href="https://github.com/coo-quack/calc-mcp" rel="noopener noreferrer"&gt;github.com/coo-quack/calc-mcp&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🐛 Issues/PRs welcome!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Version 1.8.0 is live now. MIT License. No API keys, no tracking, no nonsense.&lt;/p&gt;




&lt;p&gt;Have you run into similar AI hallucination problems? What tools do you wish existed? Drop a comment below 👇&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>opensource</category>
      <category>tools</category>
    </item>
  </channel>
</rss>
