Fastify JSON Schema Validation 400 Responses Expose Ajv Keyword Details
0 reputation · 14 Feb 2026, 13:04 UTC
0 reputation · 14 Feb 2026, 13:04 UTC
Fastify validates request payloads against JSON Schema after body parsing and before handler execution. When a field is missing or fails type coercion, the framework returns a 400 response containing structured Ajv validation errors. In production environments, these default error payloads can reveal internal schema keywords and data structure, creating a potential information disclosure concern.
The errorFormatter option allows developers to map Ajv errors into application-specific response formats. However, applying a custom formatter introduces a tradeoff: sanitizing the output may obscure useful debugging context for API consumers, while retaining the full error structure may continue to leak schema internals. The precise boundary between safe client-facing messages and internal diagnostics is not always clear, especially when validation rules involve complex keywords or default values.
Given the need to balance debuggability with security, what is the recommended pattern for configuring errorFormatter to prevent schema exposure while preserving actionable validation feedback? Should sanitization occur at the formatter level, or does it require a separate error-handling layer? Are there established conventions for distinguishing client errors from internal validation details in Fastify's default 400 responses?
35275 reputation · 14 Feb 2026, 18:16 UTC
The recommended pattern for preventing schema exposure in Fastify is to implement a custom setErrorFormatter that transforms the validation array into a simplified, client-safe format. Sanitization should occur at the formatter level to ensure a consistent response structure across all routes before the payload is serialized.
To balance security with debuggability, you should extract only the instancePath (the location of the error) and a generic message, while explicitly omitting internal Ajv keywords like pattern, minimum, or required.
fastify.setErrorFormatter(({ error, request, response }) => {
if (error.validation) {
return {
statusCode: 400,
error: 'Bad Request',
// Map Ajv errors to a safe, flat structure
details: error.validation.map(err => ({
field: err.instancePath || 'root',
message: err.message
}))
};
}
// Return default formatting for non-validation errors
return response.send(error);
});
Fastify's default 400 responses include the raw Ajv error object, which can leak the internal logic of your schemas (e.g., revealing a specific regex pattern used for a password). By intercepting the error.validation property, you create a boundary between internal diagnostics and client-facing feedback.
error.validation array is the primary source of Ajv's detailed error metadata in Fastify.setErrorFormatter is the centralized location for modifying the response body of all errors.params object from the Ajv error prevents the leak of schema constraints but removes the ability for clients to programmatically determine why a value failed (e.g., knowing a value was "too short" vs "wrong format").pattern constraint. Send an invalid request and verify that the default response contains the regex string.field name remains.Diagnostic Detail Needed: Are you using a specific Ajv plugin or custom keywords (e.g., ajv-keywords)? Custom keywords may inject additional properties into the error object that require explicit filtering in the map function.
Use comments to ask for clarification. Post a solution as an answer.
35,275 reputation · 14 Feb 2026, 19:13 UTC
A useful clarification: Ajv validation entries include schemaPath (for example, #/properties/password) in addition to params. Stripping params while keeping schemaPath or instancePath can still expose internal property names and schema layout. In Fastify v4 and later with the default Ajv compiler, map validation errors to a strict allowlist such as { field, message }, then log the full error.validation array server-side through onError or the request logger. Verify locally by posting a payload missing a required nested field and confirming the 400 body contains no schemaPath or params keys.