Skip to content

Security · AI

You shipped it in an afternoon. It's also leaking sessions.

AI assistants like Cursor optimise for “it works”, not “it's safe”. How a single review can steal every visitor's session, and the three changes that stop it.

Watch on YouTube ↗ Playing the video loads YouTube in your browser.

Contents

An AI coding assistant like Cursor will take you from an idea to a deployed app in an afternoon. Ask for a login and a reviews section and you get both, instantly. It looks clean, it runs, the UI responds. Ship it.

Here is the part most people never check: the assistant optimised for “it works”, not for “it’s safe”. Those are not the same request, and nothing in your prompt made it the second one.

In the video above we built exactly that kind of app and showed, end to end, how little it takes to walk out with a user’s session.

What “it works” quietly skips

When you ask an AI to build a comments or reviews feature, it does the obvious thing: take the text, store it, show it. If the generated code never sanitises or escapes what users type, you’ve shipped a stored XSS (cross-site scripting) hole — and it ships looking exactly like a working feature.

How the attack works

The scenario is almost boringly simple:

  1. Nothing gets “hacked.” The attacker doesn’t touch your database or your server. They leave a review — ordinary-looking text that carries a crafted HTML tag.
  2. The browser runs it. When the next visitor, or an admin, opens the page, the app renders that stored text as markup instead of plain text, and the script hidden inside it executes in their browser.
  3. The session walks out. That script reads the session token or auth cookie and sends it to the attacker. No click, no warning, nothing the victim could notice.

The full chain, running on a live app, is in the video.

Why the AI left the hole

AI is a genuine accelerator, but it has no idea what your app is worth defending against. “Build me a login and a reviews section” is a feature request. Security is a separate instruction, and if you didn’t give it, the model won’t volunteer it.

The recurring gaps when you trust generated code wholesale:

  • No real input validation on what users submit.
  • User text rendered as raw HTML, unescaped.
  • Auth tokens parked somewhere JavaScript can read them — often localStorage, which is exactly what an XSS payload reaches for.

Speed is the whole point of these tools. A breach undoes a reputation just as fast.

Three changes that close it

At guwAPI we use these tools at full tilt, then run every line through an engineering and security filter before it ships. Against XSS and token theft, three habits do most of the work.

How we prevent it

1. Escape on output, always. User input is never rendered as markup. Rendering it as text — the framework’s default escaping — is the baseline:

// Risky: rendering raw, user-supplied HTML
<div dangerouslySetInnerHTML={{ __html: review.body }} />

// Safe: render as text — the framework escapes it for you
<div>{review.body}</div>

2. Keep the token out of JavaScript’s reach. Store it in an HttpOnly cookie, so a script in the page simply cannot read it — which neutralises the token theft even if an XSS slips through:

res.cookie("session", token, {
  httpOnly: true, // not readable via document.cookie
  secure: true,   // HTTPS only
  sameSite: "strict",
});

3. Add a Content Security Policy. A CSP header tells the browser to refuse unauthorised scripts, a second wall behind your escaping:

Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self'

None of this slows you down. It’s the difference between a prototype and something you can actually put a business on.

Sources

  1. Cross Site Scripting Prevention Cheat Sheet OWASP
  2. Set-Cookie: HttpOnly MDN Web Docs
  3. Content Security Policy (CSP) MDN Web Docs

Start a conversation

Tell me what's going on