DEV Community

Cover image for Your AI code reviewer treats .svelte files like plain JS. That's how secrets leak to the browser.
Isabelle Hue
Isabelle Hue

Posted on Originally published at dev.to

Your AI code reviewer treats .svelte files like plain JS. That's how secrets leak to the browser.

I build SvelteKit apps, and I lean on AI PR review like everyone else. But I kept noticing the generic reviewers green-lighting things that would absolutely break in production — because they parse a .svelte file as if it were a .js file and miss the framework rules entirely. So I built a reviewer that actually knows Svelte 5. Here's the footgun that pushed me over the edge.

The problem

SvelteKit has a hard rule: anything from $env/static/private (or $env/dynamic/private) must never reach client code. The compiler enforces it in a lot of cases, but not all — and the moment a private value flows through a regular module or a shared util, it can end up bundled into the browser.

Here's a snippet that looks completely innocent in a PR diff:

<!-- src/routes/contact/+page.svelte -->
<script>
  import { SENDGRID_API_KEY } from '$env/static/private';

  async function submit(e) {
    e.preventDefault();
    // "just calling the API directly from the component, ship it"
    await fetch('https://api.sendgrid.com/v3/mail/send', {
      method: 'POST',
      headers: { Authorization: `Bearer ${SENDGRID_API_KEY}` },
      body: new FormData(e.target)
    });
  }
</script>

<form on:submit={submit}>
  <input name="email" type="email" />
  <button>Send</button>
</form>
Enter fullscreen mode Exit fullscreen mode

A generic AI reviewer reads this and sees: an import, a fetch, a form handler. Looks fine. It might even compliment your error handling.

What it misses: this is a .svelte component — it runs in the browser. SENDGRID_API_KEY gets shipped to every visitor's devtools. That's not a style nit, that's your provider key in plaintext on the public internet.

Same category of misses I kept seeing:

<script>
  // crashes SSR — window doesn't exist on the server
  const theme = localStorage.getItem('theme');
</script>
Enter fullscreen mode Exit fullscreen mode
<!-- XSS: user-controlled string rendered as raw HTML -->
{@html comment.body}
Enter fullscreen mode Exit fullscreen mode
<script>
  // reactivity silently lost — destructuring breaks the $state proxy
  let { count } = $state({ count: 0 });
  // mutating `count` now updates nothing
</script>
Enter fullscreen mode Exit fullscreen mode

None of these are exotic. They're the everyday mistakes you make at 1am, and they all pass a JS-shaped review.

How it works

Svelte Autopilot is a GitHub Action that runs on every pull request. It's specialized for Svelte 5 / SvelteKit, so it reviews .svelte files with the framework's actual rules in mind, not generic JS lint:

  • Private env in client code — flags $env/static/private / $env/dynamic/private reaching anything that ships to the browser.
  • SSR-unsafe top-level accesswindow, document, localStorage at module/component top level.
  • {@html} on user input — XSS surface.
  • Rune reactivity bugs — destructured $state losing reactivity, $effect used where $derived is correct, and similar Svelte 5 rune mistakes.

It posts findings as a normal PR review with the line and the why. I tested it deliberately: gpt-4o catches all four categories above; generic reviewers and gpt-4o-mini miss them. That gap is the whole reason the product exists.

Try it

Free GitHub Action on the Marketplace — drop it in your workflow and it reviews your next PR. There's also a hosted Pro app if you'd rather not manage the workflow and keys yourself: https://svelte.useautopilot.dev

Repo + source: https://github.com/isabellehuecloser-ctrl/svelte-autopilot

If you ship SvelteKit, I'd genuinely like to know whether it catches something real in your repo. Build-in-public, so feedback is the point.

Top comments (0)