XSSI Attack in React.js: What It Is and How to Crush It (with Code)
Cross-Site Script Inclusion (XSSI) is a sneaky class of data-leak bugs where an attacker’s page loads your sensitive endpoints as if they were scripts (e.g., via <script src="https://api.example.com/me">
). If your API returns JSON that can be interpreted as JavaScript—or if the attacker can exploit JS parsing tricks—private data may leak. In modern SPAs, understanding and stopping an XSSI Attack in React.js is table stakes.
This guide breaks down how XSSI works, why React apps are impacted, and the best 7 engineering patterns (server + client) to block it—complete with Node/Express, NGINX, and React code you can paste into production. Throughout, I’ll reference stronger CORS, SameSite/CSRF patterns, CSP hardening, the protective JSON prefix (aka anti-XSSI prefix), and secure fetch utilities your team can adopt in minutes.
Goal: After this article, your team should be able to identify and stop an XSSI Attack in React.js before it ships.
Quick Primer: Why “Script Inclusion” Leaks Data
- Browsers historically allow
<script src="…">
from other origins. - If a sensitive endpoint returns something a JS engine will happily execute (JSONP, or JSON that doubles as valid JS), an attacker might capture that data via clever hooks (e.g., overridden constructors, globals, callbacks).
- Even if the response is JSON, the interpreter for a
<script>
tag is JavaScript, not JSON. That mismatch is the root of many XSSI stories.
React itself doesn’t cause XSSI—your API design and headers do. But because React apps often consume JSON APIs with cookies for auth, they’re prime targets if responses aren’t guarded.
TL;DR: The Best 7 Defenses Against an XSSI Attack in React.js
- Add an Anti-XSSI JSON Prefix to all JSON responses (e.g.,
)]}',\n
) and strip it in the client beforeJSON.parse
. - Disable JSONP & JavaScript-like responses—return pure JSON and never executable JS for data.
- Harden CORS (no
*
with credentials; tightAccess-Control-Allow-Origin
; preflight). - Set
X-Content-Type-Options: nosniff
and correctContent-Type: application/json
. - Use SameSite cookies + CSRF tokens for sensitive actions and data.
- CSP with strict
script-src
and no untrusted script hosts; eliminate JSONP endpoints entirely. - Origin/Referer checks for sensitive reads; prefer
POST
+ CSRF for truly private data.
Below are detailed, copy-ready examples.
Screenshot of our free Website Vulnerability Scanner tool page
1) Server-Side: Add an Anti-XSSI JSON Prefix
Express middleware to prepend )]}',\n
to every JSON response:
// xssi-prefix.js
export function xssiPrefix(req, res, next) {
const originalJson = res.json.bind(res);
res.json = function (data) {
// Always send JSON as text with a safe prefix
const body = ")]}',\n" + JSON.stringify(data);
res.setHeader('Content-Type', 'application/json; charset=utf-8');
res.setHeader('X-Content-Type-Options', 'nosniff');
return res.send(body);
};
next();
}
// app.js
import express from 'express';
import { xssiPrefix } from './xssi-prefix.js';
const app = express();
app.use(xssiPrefix);
app.get('/api/me', (req, res) => {
res.json({ user: { id: 42, email: '[email protected]' } });
});
app.listen(3000, () => console.log('API running on :3000'));
Why this works: If an attacker includes your JSON endpoint via <script>
, the first token )]}',
causes a syntax error—so JavaScript won’t execute or leak the object. Your React client simply trims the prefix before parsing.
2) React: Safe JSON Fetch That Strips the Prefix
Create a tiny utility for your app:
// safeFetch.ts
export async function safeJson<T = unknown>(input: RequestInfo, init?: RequestInit): Promise<T> {
const res = await fetch(input, {
credentials: 'include', // if you rely on cookies
mode: 'cors',
...init,
headers: {
'Accept': 'application/json',
...(init?.headers || {})
}
});
const text = await res.text();
// Strip the anti-XSSI prefix if present
const sanitized = text.startsWith(")]}',") ? text.replace(/^.\]\}',\s?\n?/, '') : text;
try {
return JSON.parse(sanitized) as T;
} catch (e) {
throw new Error(`Invalid JSON response from ${res.url}`);
}
}
Usage in a component:
// Profile.tsx
import { useEffect, useState } from 'react';
import { safeJson } from './safeFetch';
type User = { id: number; email: string; };
export default function Profile() {
const [user, setUser] = useState<User | null>(null);
const [err, setErr] = useState<string | null>(null);
useEffect(() => {
safeJson<{ user: User }>('/api/me')
.then(({ user }) => setUser(user))
.catch((e) => setErr(e.message));
}, []);
if (err) return <p role="alert">Error: {err}</p>;
if (!user) return <p>Loading…</p>;
return <div><h2>Welcome, {user.email}</h2></div>;
}
This pattern helps you safely consume prefixed responses, a core defense for an XSSI Attack in React.js.
3) Disable JSONP & “Executable JSON”
If you still support JSONP or endpoints returning JavaScript (e.g., application/javascript
with data), turn it off:
// express.jsonp callback disabled
app.set('jsonp callback name', null); // or simply never implement JSONP
Policy: All data endpoints must return Content-Type: application/json
. No arbitrary JS execution for data.
4) CORS: Strict, Credential-Aware Rules (Server + CDN)
Do this:
- Only allow known origins.
- When using cookies (credentials), never use
*
forAccess-Control-Allow-Origin
.
Express example:
import cors from 'cors';
const allowedOrigins = ['https://app.example.com', 'https://admin.example.com'];
app.use(cors({
origin(origin, cb) {
if (!origin) return cb(null, false); // block non-origin contexts
return cb(null, allowedOrigins.includes(origin));
},
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization', 'X-CSRF-Token']
}));
NGINX example:
location /api/ {
if ($http_origin ~* ^https://(app|admin)\.example\.com$ ) {
add_header Access-Control-Allow-Origin $http_origin always;
add_header Vary Origin always;
add_header Access-Control-Allow-Credentials true always;
add_header Access-Control-Allow-Headers "Content-Type, Authorization, X-CSRF-Token" always;
add_header Access-Control-Allow-Methods "GET,POST,PUT,DELETE,OPTIONS" always;
}
}
Tight CORS reduces attack surface for an XSSI Attack in React.js by stopping unauthorized cross-origin reads (especially with credentials).
5) X-Content-Type-Options: nosniff
+ Correct MIME
Always set for JSON endpoints:
res.setHeader('Content-Type', 'application/json; charset=utf-8');
res.setHeader('X-Content-Type-Options', 'nosniff');
This prevents MIME sniffing so browsers don’t treat JSON as script in edge cases.
6) SameSite Cookies + CSRF for Sensitive Reads
Set cookies to SameSite=Lax
or Strict
, and require a CSRF token for endpoints that return sensitive data (not just state-changing actions).
Cookie (Express + cookie lib):
res.cookie('session', token, {
httpOnly: true,
secure: true,
sameSite: 'Lax', // or 'Strict' if UX allows
path: '/'
});
CSRF token check (server):
// pseudo-middleware
function requireCsrf(req, res, next) {
const token = req.get('X-CSRF-Token');
if (!token || token !== req.session.csrfToken) {
return res.status(403).json({ error: 'CSRF token invalid' });
}
next();
}
React fetch with CSRF:
const csrf = localStorage.getItem('csrf'); // or from a meta tag after login
await fetch('/api/private/data', {
method: 'POST', // prefer POST for sensitive reads, with CSRF
credentials: 'include',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': csrf ?? ''
},
body: JSON.stringify({ request: 'profile' })
});
This combination blocks common paths used in an XSSI Attack in React.js.
7) CSP: Lock Down Script Sources
CSP header example:
Content-Security-Policy: script-src 'self' https://cdn.example.com; object-src 'none'; base-uri 'self'; frame-ancestors 'self'
- Avoid
unsafe-inline
(use nonces/hashes if needed). - Never allow script loads from untrusted origins.
- Remove legacy JSONP endpoints so CSP can be strict without breaking apps.
Bonus: Detect JSON Served as JavaScript (Automated Test)
Add a test to ensure endpoints cannot execute as script:
import fetch from 'node-fetch';
test('JSON endpoints not executable as script', async () => {
const res = await fetch('https://api.example.com/me', {
headers: { 'Accept': 'application/json' }
});
const text = await res.text();
// Must start with anti-XSSI prefix
expect(text.startsWith(")]}',")).toBe(true);
// And content-type must be JSON
expect(res.headers.get('content-type')).toMatch(/application\/json/);
});
Sample vulnerability report from our free tool to check Website Vulnerability
Developer Checklist (Copy/Paste)
- JSON prefix (
)]}',\n
) on all JSON responses - Client strips prefix before
JSON.parse
Content-Type: application/json
+X-Content-Type-Options: nosniff
- CORS: specific origins,
credentials: true
, no*
with cookies - Cookies:
Secure
,HttpOnly
,SameSite=Lax/Strict
- CSRF token for sensitive reads (prefer
POST
) - No JSONP / executable JS data endpoints
- CSP
script-src
restricted; no untrusted CDNs
These stop an XSSI Attack in React.js without degrading UX.
Further Reading
- Fix server-side access issues related to data exposure:
Fix Broken Access Control in WordPress - Previous posts on cybersrely.com (great companions to stopping an XSSI Attack):
Services & Backlinks
Managed IT Services
Need proactive patching, monitoring, and secure cloud baselines? Explore our Managed IT Services for ongoing protection against issues that lead to an XSSI Attack in React.js.
👉 https://www.pentesttesting.com/managed-it-services/
AI Application Cybersecurity
Building LLMs or ML-backed apps? We harden AI pipelines and inference APIs to resist data leaks and XSSI-like abuse patterns.
👉 https://www.pentesttesting.com/ai-application-cybersecurity/
Offer Cybersecurity Service to Your Client
Agencies: white-label penetration testing and app security—deliverables your clients will love.
👉 https://www.pentesttesting.com/offer-cybersecurity-service-to-your-client/
Talk to Us
Questions about an XSSI Attack in React.js in your environment? Get an expert review.
👉 https://www.cybersrely.com/contact-us/
Extra Code Patterns You Can Reuse
Sanitize global JSON parsing in a single place:
// jsonClient.ts
type ParseOptions = { stripPrefix?: boolean };
export async function jsonClient<T>(url: string, init: RequestInit = {}, opts: ParseOptions = { stripPrefix: true }) {
const res = await fetch(url, { credentials: 'include', ...init });
const raw = await res.text();
const body = opts.stripPrefix && raw.startsWith(")]}',") ? raw.slice(5).trimStart() : raw;
if (!res.ok) throw new Error(`HTTP ${res.status}: ${body.slice(0, 120)}`);
return JSON.parse(body) as T;
}
Block JSONP at the edge (NGINX):
location ~* \.(jsonp|js)$ {
return 410; # kill legacy JS data endpoints
}
Origin/Referer check (Express):
const allowed = new Set(['https://app.example.com']);
function checkOrigin(req, res, next) {
const origin = req.get('Origin') || '';
const referer = req.get('Referer') || '';
if ([...allowed].some(a => origin.startsWith(a) || referer.startsWith(a))) return next();
return res.status(403).json({ error: 'Forbidden' });
}
Final Notes
By applying the 7 patterns above, your team dramatically reduces the chance of a successful XSSI Attack in React.js. Pair these controls with regular scanning and code review to keep regressions from sneaking back in.
If you want a hands-on review or a quick security sprint, reach out via our Contact Us page.
🔐 Frequently Asked Questions (FAQs)
Find answers to commonly asked questions about XSSI Attack in React.js.