Back to blog

Offensive Security in Practice: XSS, SQL Injection and Zero-Trust Architecture in Next.js

3/8/2026 · 2 min · Cybersecurity

Share

Offensive Security in Practice: XSS, SQL Injection and Zero-Trust Architecture in Next.js

This guide consolidates critical flaws I found during SaaS security audits. The impact is not only technical; it is financial, reputational and legal. The goal is to document practical security engineering with production mindset.

Modern frameworks do not automatically produce secure systems. Security is architecture and operational discipline across input, persistence, session and execution layers.

1) SQL Injection lives in persistence decisions

Unsafe interpolation remains a primary breach vector.

const { email, password } = req.body;
const sql = `SELECT * FROM users WHERE email = '${email}' AND password = '${password}'`;
const result = await db.query(sql);

Use prepared statements to separate command from data:

const sql = "SELECT * FROM users WHERE email = ? AND password = ?";
const result = await db.query(sql, [email, password]);

Even with ORMs, avoid unsafe raw query paths. Prefer typed methods and safe parameterized raw queries only when strictly necessary.

2) Stored XSS in multi-tenant SaaS

dangerouslySetInnerHTML without sanitization turns user content into script execution.

import DOMPurify from "dompurify";

const safeContent = DOMPurify.sanitize(userInput);
<div dangerouslySetInnerHTML={{ __html: safeContent }} />

Use dual-layer defense:

Add CSP to reduce exploitability and exfiltration:

Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted.scripts.com;

3) Token handling: stop using localStorage for sensitive session data

XSS + localStorage usually means immediate token theft.

Use server-managed cookies with:

With NextAuth, do not expose full access tokens in client session payloads. Keep sensitive tokens server-side and send only minimal session metadata.

4) Validation as security contract with Zod

export const userSchema = z.object({
  name: z.string().min(3).trim(),
  email: z.string().email().toLowerCase(),
  role: z.enum(["USER", "CLIENT"]).default("USER"),
}).strict();
const parsed = userSchema.safeParse(req.body);
if (!parsed.success) return res.status(400).json(parsed.error.format());

strict() blocks extra untrusted fields (mass-assignment attempts).

5) Consolidated zero-trust model

LayerRecommended practice
InputBackend Zod validation + .strict()
PersistencePrepared statements + least-privilege DB users
SessionHttpOnly/Secure cookies + token rotation
ExecutionStrict CSP + DOMPurify sanitization
ObservabilityAuth anomaly logs + request correlation

Conclusion

Security is not an end-of-project feature; it is the foundation.

Frameworks do not create XSS by themselves. Developers do when they trust input. ORMs do not block SQL injection if unsafe raw execution is introduced. If these fundamentals are ignored in 2026, production is carrying avoidable technical and legal risk.

CC BY-NC

This post is licensed under CC BY-NC.

Comments

Join the discussion below.