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)