Input Validation vs Output Encoding
debt(d7/e5/b7/t7)
Closest to 'only careful code review or runtime testing' (d7). The detection_hints specify phpstan, psalm, and semgrep with automated=no. These specialist tools can flag unvalidated input in some patterns, but the code_pattern note shows it requires recognising absence of validation — something these tools cannot reliably catch automatically, pushing it toward d7 rather than d5. Missing validation is typically found via manual code review or when attacks succeed in production.
Closest to 'touches multiple files / significant refactor in one component' (e5). The quick_fix describes validating type, format, length, and range at entry points across web, api, and cli contexts. Retrofitting server-side validation across all entry points — user input, API payloads, file uploads, query strings — touches multiple controllers, request handlers, and possibly a shared validation layer. This is more than a single-line fix but less than a full architectural rework.
Closest to 'strong gravitational pull' (b7). The applies_to covers web, api, and cli contexts from PHP 5.0 onward, meaning every external data entry point in the system must account for this choice. The common_mistakes include second-order attacks (stored data reused without re-validation), meaning validation decisions at ingestion shape how data is trusted throughout the entire codebase. Every new feature accepting external input must be written with this discipline in mind.
Closest to 'serious trap (contradicts how a similar concept works elsewhere)' (t7). The misconception field directly states the canonical wrong belief: 'Client-side validation is sufficient if it covers all cases.' This is a well-documented but persistently harmful mistake — developers coming from frontend contexts naturally conflate UX validation with security validation. The common_mistakes reinforce this: trusting client-side JavaScript, validating format but not range, and blocklisting instead of allowlisting are all intuitive-seeming but wrong approaches that a competent developer can easily fall into.
Also Known As
TL;DR
Explanation
Input validation and output encoding are complementary — not alternatives. Validation (reject unexpected characters, enforce length and format) reduces the attack surface and catches mistakes early. Output encoding (htmlspecialchars for HTML, parameterised queries for SQL, json_encode for JS) makes data structurally safe for its destination context. Relying on validation alone fails because valid-looking data can still be malicious in a different context. Encode for the output context regardless of how the input was validated.
Common Misconception
Why It Matters
Common Mistakes
- Relying on client-side (JavaScript) validation alone — it is trivially bypassed with browser dev tools.
- Validating format but not range — accepting a birth year of 9999 is a validation failure.
- Blocklisting known bad input instead of allowlisting known good — attackers always find new bypass strings.
- Validating on the way in but re-using stored data later without re-validation (second-order attacks).
Avoid When
- Validating only on the client side — client-side validation is for UX; server-side validation is for security.
- Using validation as a substitute for parameterised queries — validate AND use prepared statements.
- Allowlist validation that is too permissive — a regex that allows < and > defeats XSS protection.
- Validating after business logic has already acted on the input — validate at the entry point, before any processing.
When To Use
- Every piece of data entering the system from an external source — user input, API payloads, file uploads, query strings.
- Allowlist validation (accept only known-good patterns) rather than blocklist (reject known-bad).
- Before passing data to any downstream system — database, shell command, template engine, serialiser.
- Structured data like emails, URLs, dates — use format-specific validators, not generic string checks.
Code Examples
// Sanitising instead of validating — trying to fix bad input rather than reject it
$email = strip_tags($_POST['email']);
$age = (int) $_POST['age']; // -1 passes silently
// Validate — reject anything that doesn't match expectations
$email = filter_var($_POST['email'] ?? '', FILTER_VALIDATE_EMAIL)
?: throw new \InvalidArgumentException('Invalid email');
$age = filter_var($_POST['age'] ?? '', FILTER_VALIDATE_INT,
['options' => ['min_range' => 0, 'max_range' => 150]])
?: throw new \InvalidArgumentException('Invalid age');
// In Laravel — Form Request
class StoreUserRequest extends FormRequest {
public function rules(): array {
return [
'email' => ['required', 'email:rfc,dns', 'max:255', 'unique:users'],
'age' => ['required', 'integer', 'between:0,150'],
'role' => ['required', Rule::in(['user', 'editor'])],
];
}
}