Input validation vs output encoding: which to use when
input validation strategy, allowlist vs denylist, output encoding, context-aware escaping, joi schema validation, DOMPurify, HTML encoding
Input Validation vs Output Encoding
These are two distinct defenses that operate at different points in a data's lifecycle. Both are necessary. Relying on only one leaves gaps.
Input Validation
Validation checks that input conforms to an expected shape at ingestion time. Use an allowlist (define what's valid) not a denylist (block known bad characters). Denylists are always incomplete.
const Joi = require('joi');
const schema = Joi.object({
username: Joi.string().alphanum().min(3).max(30).required(),
email: Joi.string().email().required(),
age: Joi.number().integer().min(0).max(120)
});
const { error, value } = schema.validate(req.body);
if (error) return res.status(400).json({ error: error.details[0].message });Output Encoding
Encoding transforms data into a safe representation for the specific output context. The same string needs different encoding depending on where it goes.
// HTML context — encode &, <, >, ", '
const he = require('he');
const safeHtml = he.encode(userInput);
// For React — it encodes automatically, but watch dangerouslySetInnerHTML
// Never do this with user input:
return ;
// If rich HTML is required, sanitize with DOMPurify:
import DOMPurify from 'dompurify';
const clean = DOMPurify.sanitize(userInput);The Rule
Validate at ingestion (server-side, always). Encode at output, for the specific context: HTML, SQL, shell arguments, URL parameters each need different encoding. Defense-in-depth means doing both—validation narrows what can enter, encoding neutralizes anything that slips through.
