| |

REST API 09 ๐ŸŒ Input Validation, Sanitization, and Error Handling

โฑ๏ธ Estimated read: 12 min | |

Every field in a request body, query string, or header is attacker-controlled until proven otherwise . Input validation is the first gate that rejects malformed data before it reaches business logic, the database, or the shell. Sanitization is a different operation that removes or transforms dangerous content within data that is otherwise allowedโ€”and it is not a substitute for output encoding or parameterized queries. Error handling is the third piece: it determines what the client learns when validation fails, and it must be structured, consistent, and free of internal details that aid an attacker.

The three concepts are often conflated, and the confusion produces insecure systems. Validation checks whether data meets the application’s requirementsโ€”a type, a format, a length, a range, a membership in an allowed set . Sanitization modifies the data to make it safe for a specific context, such as stripping HTML tags from user-authored content before storing it . Error handling reports the failure without leaking stack traces, SQL fragments, or internal file paths . None of these replaces parameterized queries or context-aware output encoding, which are the actual defenses against injection .

Key point: Validate on the server with an allowlist of expected formats and reject anything outside it; sanitize only free-form content that must accept rich markup, using a maintained library; and return structured, generic error responses (RFC 7807 application/problem+json) that tell the client what failed without revealing internal implementation details.


Why validation, sanitization, and error handling are distinct

The injection problem. SQL injection, NoSQL injection, and command injection all trace back to the same root cause: untrusted input is interpreted as code or query structure instead of data . Validation reduces the attack surface by rejecting malformed input before it reaches the query, but it is not the primary defense. The primary defense is parameterized queries, which separate the query structure from the data so that a quote in the input is always a quote and never a delimiter . Validation is defense in depth, not the wall itself.

The XSS problem. Cross-site scripting occurs when untrusted data is placed into an HTML context without the correct output encoding . Input validation cannot prevent XSS because the same string may be safe in one context (a database column) and dangerous in another (an HTML attribute). The correct defense is context-aware output encoding at the point of rendering, and HTML sanitization only when the application must accept user-authored HTML . Sanitization is not validationโ€”it modifies the data rather than rejecting it.

The mass assignment problem. A client sends {"role": "admin"} in a profile update, and the server binds the entire payload to the user model. The role field, which should never be client-controlled, is written . Validation against a schema that rejects unknown fields, or binding only to an explicit allowlist of fields, prevents this. The schema’s additionalProperties: false setting is the mechanism that rejects fields the API does not intend to accept .

The error leakage problem. A validation failure that returns the raw database error, the stack trace, or the internal query helps the attacker understand the system’s structure and find injection points . The error response should tell the client which field failed and why, in a structured format, without the internal details. RFC 7807 defines the application/problem+json media type for exactly this: a type URI, a title, a status, a detail, and optional extension fields for field-level errors .


a. Server-side validation with schemas and allowlists

The validation rule is: parse safely, then validate the parsed values. The parser must be configured with size and depth limits before it processes the input, because a schema check after parsing cannot protect a parser that has already exhausted memory . The schema then enforces the type, the format, the length, the range, and the allowed fields.

A JSON schema with additionalProperties: false rejects unknown fields. The required array lists the fields that must be present. The properties object defines the type and constraints for each field .

// A strict schema for a user update payload
// additionalProperties: false rejects the "role" field
// that the client should not be able to set
{
  "type": "object",
  "additionalProperties": false,
  "required": ["displayName", "email"],
  "properties": {
    "displayName": {
      "type": "string",
      "minLength": 1,
      "maxLength": 100
    },
    "email": {
      "type": "string",
      "format": "email"
    },
    "timezone": {
      "type": "string",
      "enum": ["UTC", "America/New_York", "Europe/London"]
    }
  }
}

The application binds only the validated fields to the model. The role field is rejected by the schema before it reaches the model, and even if the schema were bypassed, the model binding should use an allowlist rather than assigning the entire payload .

The validation must happen on the server, even if the client also validates. Client-side validation is a UX feature; it is not a security control . An attacker can send a request directly to the API without using the client.


b. Sanitization for rich content and the limits of escaping

Sanitization is appropriate only when the application must accept content that contains markup. A WYSIWYG editor, a comment field that supports formatting, or a user-authored HTML page are the cases where validation alone cannot work, because the legitimate content includes tags and attributes that must be preserved .

The OWASP recommendation is a maintained HTML sanitization library, specifically DOMPurify in the JavaScript ecosystem. The library parses the HTML, removes the elements and attributes that are not in its allowlist, and returns a safe string .

// Import DOMPurify
import DOMPurify from 'dompurify';

// The untrusted HTML from the user
const dirty = '<p>Hello</p><script>alert("xss")</script>';

// Sanitize the HTML
// The script tag is removed, the p tag is preserved
const clean = DOMPurify.sanitize(dirty);
// clean is '<p>Hello</p>'

The critical caveat is that sanitization must be the last operation before the content is stored or rendered. If the content is sanitized and then modified, the modification can reintroduce the dangerous content that the sanitizer removed . The sanitizer must also be kept up to date, because browser changes and new bypasses are discovered regularly.

Sanitization is not a substitute for parameterized queries. A string that is safe to store in a database column is not automatically safe to interpolate into a SQL query, because the SQL context has different metacharacters. The database defense is parameterization, not sanitization .


c. Structured error handling with RFC 7807

The error response is part of the API contract. It should be consistent across every endpoint, machine-readable, and free of internal details. The RFC 7807 application/problem+json format provides the structure: type identifies the problem class, title is a short human-readable summary, status matches the HTTP status code, detail explains the specific occurrence, and extension fields carry the field-level errors .

// A validation error response using RFC 7807
// The "invalid_params" extension carries the field-level errors
{
  "type": "https://api.example.com/errors/validation",
  "title": "Request validation failed",
  "status": 400,
  "detail": "One or more fields are invalid.",
  "invalid_params": [
    { "name": "email", "reason": "must be a valid email address" },
    { "name": "displayName", "reason": "must be between 1 and 100 characters" }
  ]
}

The response status code must match the problem. A validation failure is 400 Bad Request or 422 Unprocessable Entity. An unsupported content type is 415 Unsupported Media Type. A request body that exceeds the size limit is 413 Request Entity Too Large . The status code is what the client checks first; the body is what the developer reads to understand the failure.

The error response must not include the stack trace, the SQL error, the file path, or the internal service name . Those details belong in the server logs, not in the client response. The client needs to know what to fix; the attacker should not learn how the system is built.


Complete Example Session

The following session traces a POST /users request through validation, mass-assignment rejection, and a structured error response.

// ============================================
// PART 1: THE INCOMING REQUEST
// ============================================
// The client attempts to create a user
// It includes a "role" field that should not be settable
POST /users HTTP/1.1
Content-Type: application/json

{
  "displayName": "Alice",
  "email": "alice@example.com",
  "role": "admin"
}

// ============================================
// PART 2: THE VALIDATION STEP
// ============================================
// The schema has additionalProperties: false
// The "role" field is rejected as an unknown property
// The email format is checked
// The displayName length is checked

// ============================================
// PART 3: THE STRUCTURED ERROR RESPONSE
// ============================================
// The server returns 400 Bad Request
// The response is application/problem+json
// The "invalid_params" field names the offending field
HTTP/1.1 400 Bad Request
Content-Type: application/problem+json

{
  "type": "https://api.example.com/errors/validation",
  "title": "Request validation failed",
  "status": 400,
  "detail": "The request body contains invalid fields.",
  "invalid_params": [
    { "name": "role", "reason": "is not an allowed field" }
  ]
}

// ============================================
// PART 4: THE VALID REQUEST
// ============================================
// The client removes the "role" field
POST /users HTTP/1.1
Content-Type: application/json

{
  "displayName": "Alice",
  "email": "alice@example.com"
}

// ============================================
// PART 5: THE SUCCESS RESPONSE
// ============================================
// The server creates the user with the default role
HTTP/1.1 201 Created
Location: /users/42
Content-Type: application/json

{
  "id": "usr_42",
  "displayName": "Alice",
  "email": "alice@example.com",
  "role": "member"
}

// ============================================
// PART 6: THE SERVER-SIDE BINDING
// ============================================
// Even after validation, the server binds only the
// fields it intends to accept. The role is not
// taken from the payload under any circumstances.
const ALLOWED = ['displayName', 'email', 'timezone'];
const clean = Object.fromEntries(
  Object.entries(payload).filter(([k]) => ALLOWED.includes(k))
);
// "role" is never written to the model

The six parts cover the incoming request with an unexpected field, the validation rejection, the structured error response, the corrected request, the success response, and the server-side allowlist binding. What the session demonstrates is that validation rejects the field, the error response tells the client exactly what is wrong, and the server never trusts the payload to determine the role.


Quick Reference

Validation Rules

Input TypeRule
Fixed choicesExact membership in allowed set
Numbers and datesType, format, min/max
StringsLength limit, required structure
ObjectsAllowed fields, required fields
ArraysMin/max items, validate every item

Sanitization vs Validation vs Encoding

OperationPurposeWhen
ValidationReject invalid inputEvery request
SanitizationRemove dangerous markupRich HTML content only
Output encodingPrevent XSS at renderEvery output context
ParameterizationPrevent injectionEvery database query

Error Response Structure

FieldPurpose
typeProblem class URI
titleShort summary
statusHTTP status code
detailSpecific occurrence explanation
invalid_paramsField-level errors (extension)

Status Codes for Validation

SituationStatus
Invalid field value400 or 422
Unsupported content type415
Body too large413
Missing authentication401
Insufficient permission403

Best Practices

โœ… Do This:

// Reject unknown fields with additionalProperties: false
{ "type": "object", "additionalProperties": false, "properties": { ... } }

// Bind only allowed fields to the model
const clean = allowlist(payload, ['displayName', 'email']);

// Return structured errors without internals
{ "type": "...", "title": "Validation failed", "status": 400, "invalid_params": [...] }

โŒ Don’t Do This:

// Bind the whole payload to the model
user.update(req.body); // mass assignment

// Return the database error to the client
res.status(500).json({ error: err.stack }); // leaks internals

// Rely on client-side validation only
// The attacker sends the request directly

Common Pitfalls

PitfallWhy It HappensFix
Mass assignmentBound whole payload to modelAllowlist fields
Injection despite validationUsed validation instead of parameterizationParameterize queries
XSS despite sanitizationSanitized then modifiedSanitize last, use output encoding
Stack trace in responseDebug mode in productionGeneric error message
Parser exhaustionNo size/depth limitConfigure parser limits

Real-World Examples

1. Schema with additionalProperties

{ "additionalProperties": false, "properties": { "email": { "type": "string" } } }

2. Allowlist binding

const clean = pick(req.body, ['displayName', 'email']);

3. HTML sanitization

const clean = DOMPurify.sanitize(dirtyHtml);

4. Parameterized query

db.query('SELECT * FROM users WHERE id = $1', [id]);

5. RFC 7807 error

{ "type": "...", "title": "Validation failed", "status": 400, "invalid_params": [...] }

6. Content-Type check

if (req.headers['content-type'] !== 'application/json') return res.status(415).end();

7. Request size limit

app.use(express.json({ limit: '100kb' }));

8. Log validation failures

logger.warn({ field: 'email', reason: 'invalid format' });

9. Output encoding

element.textContent = userInput; // safe, not innerHTML

10. Generic error in production

res.status(500).json({ type: '...', title: 'Internal server error' });

Visual

Validation vs Sanitization vs Encoding

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  VALIDATION                                  โ”‚
โ”‚  reject invalid input                        โ”‚
โ”‚  schema, allowlist, type, length, range      โ”‚
โ”‚  โ†’ no modification, only accept or reject    โ”‚
โ”‚                                              โ”‚
โ”‚  SANITIZATION                                โ”‚
โ”‚  modify dangerous content                    โ”‚
โ”‚  DOMPurify for HTML only                     โ”‚
โ”‚  โ†’ preserves safe markup, removes unsafe     โ”‚
โ”‚                                              โ”‚
โ”‚  OUTPUT ENCODING                             โ”‚
โ”‚  encode at render time                       โ”‚
โ”‚  context-aware (HTML, JS, URL, CSS)          โ”‚
โ”‚  โ†’ the actual XSS defense                    โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

The Error Response Contract

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  STATUS CODE โ†’ what happened                 โ”‚
โ”‚  400/422 โ†’ client input invalid              โ”‚
โ”‚  415 โ†’ wrong content type                    โ”‚
โ”‚  413 โ†’ body too large                        โ”‚
โ”‚                                              โ”‚
โ”‚  BODY โ†’ what to fix                          โ”‚
โ”‚  type: problem class                         โ”‚
โ”‚  title: summary                              โ”‚
โ”‚  invalid_params: field-level details         โ”‚
โ”‚                                              โ”‚
โ”‚  NO stack trace, NO SQL error, NO file path  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Mass Assignment Prevention

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  BAD: user.update(req.body)                  โ”‚
โ”‚  โ†’ { role: "admin" } written to model        โ”‚
โ”‚                                              โ”‚
โ”‚  GOOD: schema rejects role                   โ”‚
โ”‚  โ†’ 400 with invalid_params: [{name: "role"}] โ”‚
โ”‚                                              โ”‚
โ”‚  GOOD: allowlist binding                     โ”‚
โ”‚  โ†’ only displayName and email written        โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Summary

ItemValue
ValidationReject input outside allowed rules
SanitizationRemove dangerous markup from rich content
ParameterizationThe primary defense against injection
Output encodingThe primary defense against XSS
Mass assignmentPrevented by allowlist binding
Error formatRFC 7807 application/problem+json
Error contentGeneric, no stack traces or SQL
Status codes400/422, 415, 413

Key takeaways:

  • Validation is server-side and allowlist-based. Reject input that does not match the expected type, format, length, or allowed set. Client-side validation is UX, not security .
  • Sanitization is for rich content only. Use a maintained library like DOMPurify for HTML. Sanitization is not validation, and it is not a substitute for parameterized queries or output encoding .
  • Parameterization is the injection defense. Validation reduces the attack surface, but parameterized queries are what actually prevent SQL injection .
  • Mass assignment is prevented by schema and allowlist. Reject unknown fields with additionalProperties: false and bind only the fields the API intends to accept .
  • Errors must be structured and generic. RFC 7807 provides the format. The response tells the client what field failed, not the internal stack trace or database error .
  • Log validation failures. A burst of validation failures is a signal of an attack or a broken client. Log the field and the reason, not the raw input .

Remember: Input validation, sanitization, and error handling are three different controls with three different jobs. Validation rejects malformed data before it reaches business logic. Sanitization transforms rich content that must be accepted. Error handling reports the failure without leaking internals. None of them replaces parameterization or output encoding, and none of them is optional. The API that validates on the server, sanitizes only what it must accept, parameterizes every query, encodes every output, and returns structured generic errors is the API that survives contact with an attacker.



Stop using slow, ad-bloated tool sites! ๐Ÿคฎ

๐Ÿ”Ž Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
โœ… Finance (Mortgage, Interest, Inflation)
โœ… Tech (Base64, JSON, Dev Suite, IP)
โœ… Health (BMI, BMR, TDEE)
โœ… Productivity (Timer, Workspace, QR)

โšก๏ธ Fast & Private
๐Ÿ”’ No data leaves your device
๐Ÿ’Ž 100% Free

๐Ÿ”— Use it now: https://tools.kandz.me
๐Ÿ”– Bookmark itโ€”youโ€™ll need it later!