DEV Community

OptiRefine
OptiRefine

Posted on

How AuraWatch Finds Security Bugs Your Linter Will Never Catch

How AuraWatch Finds Security Bugs Your Linter Will Never Catch

Most security tools for developers fall into one of two camps: static linters that pattern-match on surface-level code, or AI-powered scanners that guess based on training data. Both have the same fundamental problem — they can't reason about your code. They can tell you that eval() exists, but they can't tell you whether user input actually reaches it.

AuraWatch is built differently. It uses a six-engine deterministic analysis pipeline that constructs a full semantic model of your code and reasons about it mathematically. No guessing. No false positives from pattern matching. Provable results.

AuraWatch launches on the VS Code Marketplace on May 15th, 2026.

Here's how it works under the hood.


The Problem With Existing Tools

Consider this code:

app.post('/search', async (req, res) => {
    const { term } = req.body;
    const results  = await db.query(`SELECT * FROM products WHERE name = '${term}'`);
    res.json(results);
});
Enter fullscreen mode Exit fullscreen mode

A regex-based linter might flag the template literal inside query() if it's lucky. But what about this:

app.post('/search', async (req, res) => {
    const term    = req.body.term;
    const cleaned = term.trim();
    const results = await db.query(`SELECT * FROM products WHERE name = '${cleaned}'`);
    res.json(results);
});
Enter fullscreen mode Exit fullscreen mode

The data moved through an intermediate variable. Most linters miss this entirely because they're not tracking data flow — they're just looking for patterns. AuraWatch catches both because it builds a complete picture of how data moves through your code.


The Six-Engine Pipeline

Every scan runs six independent engines in parallel. Each engine looks at the code from a different angle. Their results are merged and deduplicated before being returned.

Engine 1 — LST Analyzer (Lossless Semantic Tree)

The foundation of AuraWatch is a Lossless Semantic Tree, built using ts-morph for JavaScript/TypeScript and LibCST for Python. Unlike a standard AST, an LST preserves every token in the original source including whitespace, comments, and formatting. This matters because transformers need to reconstruct the original code faithfully when applying fixes.

The LST engine runs nine rule-based analyzers over the tree:

  • eval() calls
  • dangerouslySetInnerHTML without DOMPurify
  • Hardcoded credentials in variable declarations
  • Hardcoded credentials in object literal properties (catches { password: 'secret' } inside database config objects — a pattern most tools miss)
  • SQL injection via template literals and string concatenation
  • child_process.exec() with dynamic arguments
  • Math.random() used to generate security-sensitive values
  • Unguarded database connection calls (null dereference)
  • Deep property chains without optional chaining

Each analyzer returns a structured vulnerability object with a unique ID, CWE classification, line number, severity, and a flag indicating whether a deterministic auto-fix is available.

Engine 2 — Type-Aware Taint Analysis

This is where AuraWatch goes beyond linting. The taint engine performs inter-procedural data flow analysis to track user-controlled data from its source to a dangerous sink.

Sources are any access to HTTP request data:

req.body.username
req.query.search
req.params.id
req.headers['x-custom']
Enter fullscreen mode Exit fullscreen mode

Sinks are functions where tainted data causes harm:

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
Enter fullscreen mode Exit fullscreen mode

The engine works in three passes:

  1. Source collection — walks all variable declarations and assignments to identify variables initialized from req.* values, including destructured patterns like const { username, password } = req.body
  2. Taint propagation — marks any variable that flows from a tainted source as tainted itself
  3. Sink matching — checks whether any argument to a known sink contains a tainted variable

When a tainted variable reaches a sink, AuraWatch reports which specific variables carried the taint and exactly where the data entered the program.

Engine 3 — JS-SMT Symbolic Constraint Solver

SMT (Satisfiability Modulo Theories) solving is traditionally the domain of formal verification — not developer tooling. AuraWatch brings a lightweight SMT constraint solver directly into the VS Code extension pipeline.

The engine maintains a range map for every integer variable it encounters, tracking the possible values each variable can take given the conditional guards in scope:

// After seeing: if (count > 0)
// The engine knows: count.min = 1

// After seeing: if (count < 10)
// The engine knows: count.max = 9

// Combined: count ∈ [1, 9]
Enter fullscreen mode Exit fullscreen mode

With this range information, the engine can prove:

Division by zero:

function paginate(items, pageSize) {
    return items.length / pageSize; // SMT proves pageSize can be 0
}
Enter fullscreen mode Exit fullscreen mode

Contradictory conditions (dead code):

if (score > 100) { ... }
if (score < 50)  { ... } // SMT proves these are mutually exclusive
Enter fullscreen mode Exit fullscreen mode

Always-true auth bypasses:

if (isAdmin || true) { // SMT proves this always executes
    deleteAllUsers();
}
Enter fullscreen mode Exit fullscreen mode

For Python, AuraWatch uses the Z3 SMT solver directly via its Python bindings, enabling full logical formula construction and satisfiability checking. The JavaScript engine uses a custom lightweight interval arithmetic solver that runs in-process without any native dependencies.

Engine 4 — Async Flow and Prototype Pollution Tracker

This engine handles two distinct but related categories of JavaScript-specific vulnerabilities.

Prototype pollution occurs when user-controlled data is used as an object key in an assignment:

// User sends: { "__proto__": { "isAdmin": true } }
function merge(target, source) {
    for (const key in source) {
        target[key] = source[key]; // key = "__proto__"
    }
}
Enter fullscreen mode Exit fullscreen mode

If key equals __proto__, constructor, or prototype, this mutates the base Object prototype — affecting every object in the process. AuraWatch detects computed property assignments where the key is user-controlled, direct __proto__ mutations, and Object.assign() calls with untrusted sources.

Unhandled promise rejection crashes Node.js processes in production (since Node 15). The engine identifies promise chains that use .then() without .catch() at the expression statement level — a common oversight when converting callback-based code to async.

Engine 5 — Hallucination Defense

This engine is specifically designed for the AI-generated code era. Large language models frequently hallucinate npm package names — inventing packages that don't exist on the npm registry. When a developer installs an AI-suggested package and the real package doesn't exist, a malicious actor can publish a package with that exact name. This is a supply chain attack vector that barely existed five years ago.

AuraWatch maintains a curated registry of ~200 well-known legitimate packages. For every require() call and import statement it finds, it checks:

  1. Is the package in the known-good registry?
  2. Does the package name match a known typosquatting pattern?
  3. If a package.json is provided in the scan request, is the package listed as a dependency?

Typosquatting patterns are matched against a curated list of confusable names:

lodas  → lodash
expres → express
axioss → axios
helmt  → helmet
Enter fullscreen mode Exit fullscreen mode

A package flagged as potentially hallucinated gets a CRITICAL severity finding — because installing a non-existent package that gets claimed by an attacker is code execution on install via the postinstall lifecycle hook.

Engine 6 — Framework-Specific Sink Maps

Generic rules miss framework-specific vulnerabilities. This engine knows the dangerous patterns specific to Express, Fastify, Next.js, and React.

Express:

  • res.render(userInput) — Server-Side Template Injection when the template name is user-controlled
  • res.redirect(userInput) — Open Redirect when the destination URL comes from user input
  • res.send(\${userInput}) — Reflected XSS when user data appears in an HTML response

Next.js:

  • context.query values spread directly into page props in getServerSideProps without validation

Fastify:

  • reply.send(request.body) — echoing raw request body in responses that may be rendered as HTML

The framework is auto-detected from import statements — no configuration required.


Deterministic Auto-Fixing

When AuraWatch finds a vulnerability with a known safe transformation, it can fix it automatically using the same LST infrastructure it uses for analysis.

Fixers work as CST Transformers — they traverse the syntax tree and replace unsafe nodes with safe equivalents while preserving all surrounding formatting and comments:

Vulnerability Auto-Fix
Hardcoded secret Replaced with `process.env.VARIABLE_NAME \
{% raw %}Math.random() in security context Replaced with crypto.randomBytes(4).readUInt32BE(0)
dangerouslySetInnerHTML Wrapped with DOMPurify.sanitize(), import added
eval(x) (Python) Replaced with ast.literal_eval(x)
pickle.loads(x) (Python) Replaced with json.loads(x)
yaml.load(x) (Python) Replaced with yaml.safe_load(x)
subprocess.run(shell=True) (Python) shell=True argument removed
Unguarded async result Property accesses converted to optional chaining (?.)

Fixes are presented as a unified diff in VS Code's native diff viewer before anything is written to disk. The user reviews the patch and explicitly accepts or rejects it — AuraWatch never modifies your files without confirmation.


Architecture: Two Engines, One Interface

AuraWatch runs as two separate Cloud Run services behind a single VS Code extension:

Python Engine (LibCST + Z3)

  • Handles .py files
  • Uses LibCST for lossless CST analysis
  • Uses Z3 for formal SMT solving
  • 10 LST analyzers, 6 auto-fixers

JS/TS Engine (ts-morph + custom SMT)

  • Handles .js, .ts, .jsx, .tsx files
  • Uses ts-morph for full TypeScript-aware AST analysis
  • Custom interval arithmetic SMT solver
  • 6 base analyzers + 4 advanced engines

The VS Code extension automatically routes each scan to the correct engine based on the active file's language. The API key, billing, and auth system are unified — one account works across both engines.

Authentication uses magic links — no passwords. When a user signs in, a time-limited token is written to Firestore, emailed to the user, and validated server-side when clicked. The resulting API key is stored in VS Code's native SecretStorage API (the OS keychain) — never in plaintext settings files.


What's Next

The roadmap includes:

  • GitHub Actions integration — run the full analysis pipeline as a CI check on every pull request
  • Inline diagnostics — surface findings as VS Code squiggles directly in the editor without opening the chat panel
  • Java and Go support — extending the LST engine to additional languages
  • Cross-function taint tracking — following data flow across function boundaries and module imports

Launch

AuraWatch launches on the VS Code Marketplace on May 15th, 2026. Follow along on Twitter/X and LinkedIn to get notified on launch day. If you work in security or DevSecOps and want to give early feedback, reach out — every piece of feedback shapes what gets built next.

Top comments (0)