Input Sanitization vs Validation: Protecting Your API From Injection
Validation and sanitization are different things. Mixing them up creates security holes.
The Distinction
Validation: checking that input meets your requirements. Reject if it doesn't.
Sanitization: transforming input to make it safe. Modify and accept.
Rule of thumb: validate at your API boundary, sanitize before rendering.
SQL Injection
// VULNERABLE: string interpolation in SQL
const users = await db.query(
`SELECT * FROM users WHERE email = '${req.body.email}'`
);
// If email = "' OR '1'='1", this returns ALL users
// SAFE: parameterized queries (Prisma handles this automatically)
const users = await prisma.user.findMany({
where: { email: req.body.email }, // Always parameterized
});
// SAFE: raw queries with parameters
const users = await prisma.$queryRaw`
SELECT * FROM users WHERE email = ${req.body.email}
`;
XSS: Cross-Site Scripting
// VULNERABLE: rendering user input as HTML
const html = `<div>${userComment}</div>`; // If comment = '<script>alert(1)</script>'
// SAFE: escape HTML entities
import DOMPurify from 'dompurify';
// For React: JSX escapes by default
return <div>{userComment}</div>; // Safe — React escapes automatically
// For dangerouslySetInnerHTML: always sanitize first
return (
<div
dangerouslySetInnerHTML={{
__html: DOMPurify.sanitize(userComment)
}}
/>
);
Path Traversal
import path from 'path';
// VULNERABLE: using user input in file paths directly
const file = fs.readFileSync(`/uploads/${req.params.filename}`);
// If filename = '../../../etc/passwd', you've exposed your server
// SAFE: resolve and validate the path stays in allowed directory
const uploadsDir = '/uploads';
const requestedPath = path.resolve(uploadsDir, req.params.filename);
if (!requestedPath.startsWith(uploadsDir)) {
return res.status(403).json({ error: 'Forbidden' });
}
const file = fs.readFileSync(requestedPath);
Prototype Pollution
// VULNERABLE: merging user input into objects
function merge(target: object, source: object) {
for (const key in source) {
target[key] = source[key]; // '__proto__' key pollutes Object.prototype
}
}
// SAFE: use Zod to strip unknown keys before processing
const SafeSchema = z.object({
name: z.string(),
value: z.number(),
// Unknown keys are stripped by default in Zod
});
const safe = SafeSchema.parse(userInput);
Command Injection
import { execFile } from 'child_process';
// VULNERABLE: shell=true with user input
exec(`convert ${userFile} output.jpg`); // Never do this
// SAFE: execFile with array arguments (no shell)
execFile('convert', [userFile, 'output.jpg'], (err, stdout) => {
// Arguments are passed directly, not through shell
});
Security hardening — injection prevention, XSS protection, input validation — is production-configured in the AI SaaS Starter Kit. The MCP Security Scanner audits MCP servers for these same vulnerabilities.
Top comments (0)