<?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: Deep Ackberry</title>
    <description>The latest articles on DEV Community by Deep Ackberry (@youngbari).</description>
    <link>https://dev.to/youngbari</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%2F4210144%2Fcf82427a-4964-47fe-bd7f-eea66900f958.png</url>
      <title>DEV Community: Deep Ackberry</title>
      <link>https://dev.to/youngbari</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/youngbari"/>
    <language>en</language>
    <item>
      <title>Exorcist: a local Gemma that hunts ghosts in your node_modules</title>
      <dc:creator>Deep Ackberry</dc:creator>
      <pubDate>Sun, 01 Nov 2026 15:24:20 +0000</pubDate>
      <link>https://dev.to/youngbari/exorcist-a-local-gemma-that-hunts-ghosts-in-your-nodemodules-1bao</link>
      <guid>https://dev.to/youngbari/exorcist-a-local-gemma-that-hunts-ghosts-in-your-nodemodules-1bao</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; — A CLI that scans your installed npm packages for malicious ones. Four fast rules pick the suspects, then a Gemma 3 4B model running on your own laptop reads each one and says what it's actually doing. Nothing leaves your machine. MIT, 125 tests,&lt;br&gt;
&lt;a href="https://github.com/Ackberry/exorcist" rel="noopener noreferrer"&gt;github.com/Ackberry/exorcist&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8gdoe6fit6z67p67jcjt.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8gdoe6fit6z67p67jcjt.gif" alt="A scan of demo/victim printing line by line: five packages, three haunted, then soul-snatcher at 93 out of 100 marked remove, hexed-helper at 60 marked remove, lodasg at 28 marked swap, and two quiet packages" width="800" height="891"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  If you're not a developer
&lt;/h2&gt;

&lt;p&gt;When you install almost any modern app or website's code, it quietly pulls in hundreds of small packages written by strangers. Nobody reads them. In September 2025, attackers phished one maintainer and pushed malicious code into packages downloaded &lt;strong&gt;two billion times a week&lt;/strong&gt; — it sat there rewriting people's crypto wallet addresses.&lt;/p&gt;

&lt;p&gt;Exorcist is a tool that reads those packages before they bite, and asks an AI running on your own computer — not a company's server — whether anything looks wrong.&lt;/p&gt;
&lt;h2&gt;
  
  
  If you are a developer
&lt;/h2&gt;

&lt;p&gt;Four rules, each one file in &lt;code&gt;src/rules/&lt;/code&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Ghost&lt;/th&gt;
&lt;th&gt;Catches&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Poltergeist&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;install hooks, &lt;code&gt;child_process&lt;/code&gt;, network, &lt;code&gt;process.env&lt;/code&gt;, &lt;code&gt;.npmrc&lt;/code&gt;, &lt;code&gt;eval&lt;/code&gt;/&lt;code&gt;Function&lt;/code&gt;, base64, &lt;code&gt;\xNN&lt;/code&gt;-escaped names&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Doppelganger&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;typosquats — &lt;code&gt;lodasg&lt;/code&gt; wearing &lt;code&gt;lodash&lt;/code&gt;'s face&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Phantom&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;a direct dependency nothing imports, whose install hooks still run&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Revenant&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;a release after 365+ days of silence, or under a new maintainer&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Rules are the cheap filter — regex and lockfile arithmetic, never executed. Only flagged packages go to the model. &lt;strong&gt;The rule total is a floor: Gemma can raise a score, never lower one.&lt;/strong&gt; Each package comes back &lt;code&gt;pin&lt;/code&gt;, &lt;code&gt;swap&lt;/code&gt;, or &lt;code&gt;remove&lt;/code&gt;.&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/Ackberry/exorcist &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;exorcist
npm ci &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm run build
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--prefix&lt;/span&gt; demo/victim
node dist/cli.js demo/victim &lt;span class="nt"&gt;--no-oracle&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No Ollama needed for that. &lt;code&gt;ollama pull gemma3:4b&lt;/code&gt; and drop the flag to add verdicts.&lt;br&gt;
&lt;code&gt;--json&lt;/code&gt; pipes the report; &lt;code&gt;--fail-on 75&lt;/code&gt; exits 2 so CI can gate on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three things worth knowing
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;It's noisy, and I'd rather say so.&lt;/strong&gt; On its own 346 dependencies it flags 100 — mostly revenant findings at &lt;code&gt;pin&lt;/code&gt;, which means "shipped again after a long quiet" and is both suspicious and completely normal. One is a real false positive: &lt;code&gt;trough&lt;/code&gt; read as typosquat of &lt;code&gt;through&lt;/code&gt;. Treat the score as a starting point for a person, not a gate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I nearly improved the wrong layer.&lt;/strong&gt; I was about to write a careful prompt to catch obfuscated code. I measured first: four packages doing the identical thing scored 100, 100, 100, and &lt;strong&gt;zero&lt;/strong&gt; — the last one hid every name behind &lt;code&gt;\xNN&lt;/code&gt; escapes and called &lt;code&gt;Function("return this")()&lt;/code&gt;. A package with no findings never becomes a suspect, so the model would never have seen it. The fix belonged in the rules, not the prompt.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Local isn't a preference here.&lt;/strong&gt; A scanner reads untrusted code by definition. Pipe that to a cloud model and you've built an injection target &lt;em&gt;and&lt;/em&gt; shipped your dependency tree off the machine in one move.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb4xwvhhsg6kx8ce82v9q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb4xwvhhsg6kx8ce82v9q.png" alt="Sentry span attributes for the Gemma judge call: op gen_ai.invoke_agent, agent name Gemma judge, response model gemma3:4b, and usage of 675 input tokens, 69 output and 744 total" width="503" height="675"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Sentry tracing (opt-in, &lt;code&gt;SENTRY_DSN&lt;/code&gt;) showed the shape of it: &lt;code&gt;resolve&lt;/code&gt;, &lt;code&gt;rules&lt;/code&gt; and &lt;code&gt;score&lt;/code&gt; take about &lt;strong&gt;eleven milliseconds&lt;/strong&gt; for five packages. Everything else is the model. That's the argument for rules-as-a-filter, measured instead of assumed.&lt;/p&gt;

&lt;p&gt;Built on &lt;strong&gt;Gemma 3 4B&lt;/strong&gt; via Ollama, orchestrated as a &lt;strong&gt;Mastra&lt;/strong&gt; workflow, traced with &lt;strong&gt;Sentry&lt;/strong&gt;. The limitations are written down in &lt;a href="https://github.com/Ackberry/exorcist/blob/main/docs/STATUS.md" rel="noopener noreferrer"&gt;&lt;code&gt;docs/STATUS.md&lt;/code&gt;&lt;/a&gt;, including that it only reads source and never runs it, so a payload that only appears at runtime is outside what it can see.&lt;/p&gt;

&lt;p&gt;I started this for the Hacktoberfest Open-Source AI Challenge and missed the deadline by a few hours. Halloween got the better of me. Posting it anyway, because the thing works and the ghosts in &lt;code&gt;node_modules&lt;/code&gt; do not care what day it is.&lt;/p&gt;

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