<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[AuraWatch]]></title><description><![CDATA[AuraWatch]]></description><link>https://aurawatch.hashnode.dev</link><image><url>https://cdn.hashnode.com/uploads/logos/69f0ed0210a70b3335e0735d/ff55f299-0dca-4618-a2f1-ee679e7c661f.png</url><title>AuraWatch</title><link>https://aurawatch.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Fri, 11 Sep 2026 18:55:50 GMT</lastBuildDate><atom:link href="https://aurawatch.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[I Built a VS Code Security Extension That Uses SMT Solving and Taint Analysis — Here's What I Learned]]></title><description><![CDATA[I've been building developer tools for a while now, and one thing has always bothered me about security tooling: it's either too noisy or too shallow.
Too noisy means your linter flags eval() everywhe]]></description><link>https://aurawatch.hashnode.dev/i-built-a-vs-code-security-extension-that-uses-smt-solving-and-taint-analysis-here-s-what-i-learned</link><guid isPermaLink="true">https://aurawatch.hashnode.dev/i-built-a-vs-code-security-extension-that-uses-smt-solving-and-taint-analysis-here-s-what-i-learned</guid><dc:creator><![CDATA[Adam Corriveau]]></dc:creator><pubDate>Tue, 05 May 2026 20:59:13 GMT</pubDate><content:encoded><![CDATA[<p>I've been building developer tools for a while now, and one thing has always bothered me about security tooling: it's either too noisy or too shallow.</p>
<p>Too noisy means your linter flags <code>eval()</code> everywhere, including the one in a test file parsing a hardcoded string that will never see user input. Too shallow means it misses the SQL injection that's three variable assignments away from <code>req.body</code> because it only pattern-matches on surface-level code.</p>
<p>I wanted to build something that actually <em>reasons</em> about code the way a security auditor does — following data through the program, understanding context, and only flagging things that are genuinely dangerous. The result is <strong>AuraWatch</strong>, a VS Code extension launching May 15th, 2026.</p>
<p>This is the story of how I built it and what the architecture looks like.</p>
<hr />
<h2>Starting With the Right Foundation: Lossless Semantic Trees</h2>
<p>The first decision was the analysis foundation. Most linters use a regular Abstract Syntax Tree (AST). The problem with a standard AST is that it strips information — whitespace, comments, formatting — which is fine for analysis but breaks things when you want to write code back to disk after applying a fix.</p>
<p>I chose <strong>Lossless Semantic Trees</strong> instead. An LST preserves every token in the source file. When a transformer modifies a node and regenerates the code, the output is byte-for-byte identical to the input except for the specific change that was made. No reformatting, no lost comments, no surprise diffs.</p>
<p>For JavaScript and TypeScript I use <strong>ts-morph</strong>, which wraps the TypeScript compiler API and gives full type-aware LST access. For Python I use <strong>LibCST</strong>, which was specifically designed for lossless transformations.</p>
<p>This foundation powers both the analysis (reading the tree) and the auto-fixing (rewriting the tree).</p>
<hr />
<h2>The Part Most Tools Skip: Taint Analysis</h2>
<p>Here's the vulnerability that motivated me to go beyond simple rule matching:</p>
<pre><code class="language-javascript">app.post('/users', async (req, res) =&gt; {
    const { username } = req.body;
    const sanitized    = username.replace(/'/g, '');  // developer thinks this is safe
    const user         = await db.query(`SELECT * FROM users WHERE name = '${sanitized}'`);
    res.json(user);
});
</code></pre>
<p>A linter that pattern-matches on template literals inside <code>query()</code> will catch this. But what about:</p>
<pre><code class="language-javascript">async function getUser(name) {
    return db.query(`SELECT * FROM users WHERE name = '${name}'`);
}

app.post('/users', async (req, res) =&gt; {
    const result = await getUser(req.body.username);
    res.json(result);
});
</code></pre>
<p>Now the tainted data crosses a function boundary. The query function looks perfectly safe in isolation. The route handler looks fine too — it's just passing a string to a function. But together they form a SQL injection vulnerability.</p>
<p>AuraWatch's taint engine handles this with three passes:</p>
<p><strong>Pass 1 — Source identification.</strong> Every access to <code>req.body</code>, <code>req.query</code>, <code>req.params</code>, <code>req.headers</code>, and similar HTTP inputs is marked as a taint source. Destructured variables get marked too: <code>const { username, password } = req.body</code> marks both <code>username</code> and <code>password</code> as tainted.</p>
<p><strong>Pass 2 — Propagation.</strong> Any variable assigned from a tainted variable becomes tainted. This propagates through assignments, function arguments, and return values.</p>
<p><strong>Pass 3 — Sink checking.</strong> Known dangerous functions are checked against their arguments. If any argument contains a tainted variable, the vulnerability is flagged — along with the name of the specific variable that carried the taint and where it originated.</p>
<p>The sinks currently tracked include:</p>
<pre><code class="language-plaintext">eval()          → CWE-95  Code Injection
exec()          → CWE-78  OS Command Injection
db.query()      → CWE-89  SQL Injection
fs.readFile()   → CWE-22  Path Traversal
res.redirect()  → CWE-601 Open Redirect
innerHTML       → CWE-79  XSS
res.render()    → CWE-94  Template Injection
</code></pre>
<hr />
<h2>The Unexpected Addition: SMT Solving</h2>
<p>I didn't originally plan to include an SMT solver. It came out of a specific bug I kept seeing in code reviews — integer range errors that cause crashes in production but are completely invisible to linting.</p>
<p>SMT (Satisfiability Modulo Theories) is a form of formal verification that checks whether a mathematical formula can be satisfied. In plain terms: given what we know about a variable's constraints, can it ever equal zero? Can this branch ever execute?</p>
<p>For Python, I integrate the <strong>Z3 SMT solver</strong> directly. Z3 is a theorem prover from Microsoft Research that's used in academic formal verification. AuraWatch uses it to prove things like:</p>
<pre><code class="language-python">def process(items, page_size):
    if page_size &gt; 0:
        return items[:page_size]
    return items[0] / page_size  # Z3 proves page_size can be 0 here
</code></pre>
<p>For JavaScript, I built a lightweight <strong>interval arithmetic solver</strong> that runs in-process without native dependencies. It maintains a range map — tracking the minimum and maximum possible value of each variable given the conditional guards seen so far:</p>
<pre><code class="language-javascript">function allocate(size) {
    // No guard on size — solver knows size ∈ (-∞, +∞)
    // Can be zero or negative
    return new Array(size).fill(0); // flagged: size can be ≤ 0
}

function allocateSafe(size) {
    if (size &lt;= 0) throw new Error('Invalid size');
    // Now solver knows size ∈ (0, +∞)
    return new Array(size).fill(0); // clean
}
</code></pre>
<p>The solver also detects contradictory conditions — branches that can never execute because their conditions are mutually exclusive — and always-true conditions that bypass auth guards:</p>
<pre><code class="language-javascript">if (isAdmin || true) {
    // SMT proves this always executes regardless of isAdmin
    deleteAllRecords();
}
</code></pre>
<hr />
<h2>Defending Against AI-Generated Code: Hallucination Detection</h2>
<p>This one surprised me when I added it, but it's become one of the most useful findings in practice.</p>
<p>LLMs hallucinate package names. Not constantly, but often enough that it's a real problem. A model trained on GitHub data might confidently suggest <code>require('express-validator-plus')</code> or <code>require('mongoose-utils')</code> — packages that sound plausible but don't exist on npm.</p>
<p>This matters for security because of how npm's <code>postinstall</code> hook works. If a package doesn't exist and an attacker registers it later, anyone who <code>npm install</code>s it gets arbitrary code execution on their machine during installation. This is exactly how several high-profile supply chain attacks have worked.</p>
<p>AuraWatch maintains a registry of ~200 well-known legitimate packages and checks every <code>require()</code> and <code>import</code> against it. It also runs typosquatting detection against known confusable patterns:</p>
<pre><code class="language-plaintext">lodas    → lodash      (missing h)
expres   → express     (missing s)
mongoos  → mongoose    (missing e)
axio     → axios       (missing s)
</code></pre>
<p>A hallucinated or suspicious package gets flagged as CRITICAL — not because the code is necessarily broken today, but because a future supply chain attack could make it dangerous retroactively.</p>
<hr />
<h2>Framework-Aware Analysis</h2>
<p>Generic AST rules miss framework-specific patterns. A React developer using <code>dangerouslySetInnerHTML</code> needs different guidance than an Express developer calling <code>res.render()</code>. AuraWatch auto-detects the framework from import statements and applies the relevant sink rules.</p>
<p><strong>React:</strong> <code>dangerouslySetInnerHTML</code> without <code>DOMPurify.sanitize()</code> is flagged as XSS. The auto-fixer wraps the value with <code>DOMPurify.sanitize()</code> and adds the import if it's missing.</p>
<p><strong>Express:</strong> Dynamic <code>res.render()</code> arguments (template name from user input) flag as Server-Side Template Injection. Dynamic <code>res.redirect()</code> flags as Open Redirect.</p>
<p><strong>Next.js:</strong> <code>context.query</code> values spread into page props in <code>getServerSideProps</code> without validation are flagged — a common pattern in generated Next.js code that introduces reflected XSS.</p>
<p><strong>Fastify:</strong> <code>reply.send(request.body)</code> echoing raw request bodies in responses flags as potential XSS when the response is rendered in a browser.</p>
<hr />
<h2>Auto-Fixing: The Hard Part</h2>
<p>Analysis is the easier half. Writing fixes that are correct, safe, and preserve the original code formatting is genuinely hard.</p>
<p>Every auto-fixer in AuraWatch is a <strong>CST Transformer</strong> — a visitor that walks the lossless syntax tree and replaces specific nodes. The key constraint is that the transformer must only touch the node it's fixing. Everything else — adjacent code, comments, indentation, blank lines — must come out identical.</p>
<p>Some examples of what the fixers do:</p>
<p><strong>Hardcoded secrets:</strong></p>
<pre><code class="language-javascript">// Before
const api_key = 'sk-prod-abc123';

// After
const api_key = process.env.API_KEY || "";
</code></pre>
<p><strong>Weak PRNG in security context:</strong></p>
<pre><code class="language-javascript">// Before
const token = Math.random().toString(36);

// After
const token = require("crypto").randomBytes(4).readUInt32BE(0).toString(36);
</code></pre>
<p><strong>Unsafe YAML loading (Python):</strong></p>
<pre><code class="language-python"># Before
config = yaml.load(data)

# After
config = yaml.safe_load(data)
</code></pre>
<p>All fixes go through VS Code's native diff viewer before anything touches disk. The user sees exactly what changed and explicitly accepts or rejects it. AuraWatch never writes to a file without confirmation.</p>
<hr />
<h2>The Deployment Architecture</h2>
<p>AuraWatch runs as two independent microservices on Google Cloud Run:</p>
<p><strong>Python Engine</strong> handles <code>.py</code> files. It runs LibCST for lossless tree analysis, Z3 for SMT solving, and exposes a FastAPI endpoint that the VS Code extension calls.</p>
<p><strong>JS/TS Engine</strong> handles <code>.js</code>, <code>.ts</code>, <code>.jsx</code>, and <code>.tsx</code> files. It runs ts-morph for TypeScript-aware analysis, the custom interval arithmetic SMT solver, and exposes a Fastify endpoint.</p>
<p>The VS Code extension routes each scan to the correct engine automatically based on the active file's language ID. Authentication uses magic links — the user enters their email, receives a time-limited link, clicks it, and VS Code catches the redirect via a URI handler and stores the API key in the OS keychain via VS Code's <code>SecretStorage</code> API. No passwords, no OAuth dance, no API key pasted into a settings file.</p>
<p>Billing runs through Stripe with a Firestore backend. Free tier is 25 scans per month. Pro is unlimited.</p>
<hr />
<h2>What's on the Roadmap</h2>
<p>A few things I'm building toward after launch:</p>
<p><strong>GitHub Actions integration.</strong> The analysis engines are already HTTP APIs — wiring them into a CI workflow is straightforward. The goal is to block PRs that introduce new critical vulnerabilities.</p>
<p><strong>Inline diagnostics.</strong> Right now findings appear in the chat panel. The next step is surfacing them as editor squiggles with hover cards, the same way TypeScript type errors appear.</p>
<p><strong>Cross-function taint tracking.</strong> The current taint engine handles taint within a single function scope. Following data across function calls and module imports requires building a call graph first — that's the next major analysis milestone.</p>
<p><strong>Java and Go support.</strong> Both languages have mature CST libraries. The architecture is engine-agnostic so adding a new language is mostly writing the analyzer rules.</p>
<hr />
<h2>Launch</h2>
<p>AuraWatch launches on the VS Code Marketplace on <strong>May 15th, 2026</strong>.</p>
<p>If you're a security engineer, developer, or just someone who's been burned by a vulnerability that a smarter tool should have caught — follow along on Twitter/X and LinkedIn. And if you want to give early feedback before launch, reach out directly. I'm actively building this and I take every piece of feedback seriously.</p>
]]></content:encoded></item></channel></rss>