XSS prevention
All user string inputs are escaped server-side before use. CSP headers block unauthorized script execution.
What is XSS?
Cross-site scripting (XSS) occurs when an attacker injects malicious JavaScript into a web page that executes in another user's browser. In a typical attack, user-supplied input (a name, a comment, a URL) is stored and later rendered as HTML - and the injected script runs with full access to the page.
escapeHTML()
The primary XSS defense in NextForge is escapeHTML() in lib/sanitize.ts - a server-safe function (no DOM APIs) that converts special HTML characters to their entity equivalents:
export function escapeHTML(str: string): string {
return str
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''')
}This ensures that a value like <script>alert(1)</script> is stored and rendered as <script>alert(1)</script> - harmless text rather than executable code.
Sanitize helpers
Three higher-level helpers build on escapeHTML():
// Sanitize a single value - type-checks, trims, escapes
export function sanitizeString(str: unknown): string {
if (typeof str !== 'string') return ''
return escapeHTML(str.trim())
}
// Recursively sanitize all string values in a plain object
export function sanitizeObject<T extends Record<string, unknown>>(obj: T): T {
return Object.fromEntries(
Object.entries(obj).map(([key, value]) => [
key,
typeof value === 'string' ? sanitizeString(value) :
typeof value === 'object' && value !== null ?
sanitizeObject(value as Record<string, unknown>) : value
])
) as T
}
// Run stripMongoOperators then sanitizeObject
export function sanitizeAndStrip<T extends Record<string, unknown>>(obj: T): T {
return sanitizeObject(stripMongoOperators(obj) as T)
}CSP headers
A Content Security Policy header is applied to every response in proxy.ts, providing browser-level protection even if a value escapes server-side sanitization:
headers.set( 'Content-Security-Policy', "default-src 'self'; " + "script-src 'self' 'unsafe-inline'; " + "style-src 'self' 'unsafe-inline'; " + "img-src 'self' data: https:; " + "font-src 'self' data:" )
Usage in server actions
Every server action calls sanitizeAndStrip() as the very first step, before Zod validation and before any DB operation:
export async function loginAction(formData: FormData): Promise<ActionState> {
const raw = {
email: formData.get('email'),
password: formData.get('password'),
}
// Step 1: Sanitize - strips operators, escapes HTML
const sanitized = sanitizeAndStrip(raw)
// Step 2: Validate - Zod schema check
const parsed = parseSchema(loginSchema, sanitized)
if (!parsed.success) return parsed
// Step 3: Rate limit
// Step 4: DB operation
// ...
}