Fastify vulnerable to invalid content-type parsing, which could lead to validation bypass
Description
Fastify is a fast and low overhead web framework, for Node.js. In versions 5.0.0 to 5.3.0 as well as version 4.29.0, applications that specify different validation strategies for different content types have a possibility to bypass validation by providing a _slightly altered_ content type such as with different casing or altered whitespacing before ;. This was patched in v5.3.1, but the initial patch did not cover all problems. This has been fully patched in v5.3.2 and v4.29.1. A workaround involves not specifying individual content types in the schema.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
fastifynpm | >= 5.0.0, < 5.3.2 | 5.3.2 |
fastifynpm | >= 4.29.0, < 4.29.1 | 4.29.1 |
Affected products
1Patches
2f3d2bcb3963cfix: treat space as a delimiter in content-type parsing (#6064)
2 files changed · +32 −16
lib/validation.js+1 −1 modified@@ -261,7 +261,7 @@ function wrapValidationError (result, dataVar, schemaErrorFormatter) { */ function getEssenceMediaType (header) { if (!header) return '' - return header.split(';', 1)[0].trim().toLowerCase() + return header.split(/[ ;]/, 1)[0].trim().toLowerCase() } module.exports = {
test/schema-validation.test.js+31 −15 modified@@ -2,6 +2,7 @@ const { test } = require('tap') const Fastify = require('..') +const { request } = require('undici') const AJV = require('ajv') const Schema = require('fluent-json-schema') @@ -1342,7 +1343,7 @@ test('Schema validation when no content type is provided', async t => { }) test('Schema validation will not be bypass by different content type', async t => { - t.plan(8) + t.plan(10) const fastify = Fastify() @@ -1365,58 +1366,73 @@ test('Schema validation will not be bypass by different content type', async t = } }, async () => 'ok') - await fastify.ready() + await fastify.listen({ port: 0 }) + t.teardown(() => fastify.close()) + const address = fastify.listeningOrigin - const correct1 = await fastify.inject({ + const correct1 = await request(address, { method: 'POST', url: '/', headers: { 'content-type': 'application/json' }, - body: { foo: 'string' } + body: JSON.stringify({ foo: 'string' }) }) t.equal(correct1.statusCode, 200) + await correct1.body.dump() - const correct2 = await fastify.inject({ + const correct2 = await request(address, { method: 'POST', url: '/', headers: { 'content-type': 'application/json; charset=utf-8' }, - body: { foo: 'string' } + body: JSON.stringify({ foo: 'string' }) }) t.equal(correct2.statusCode, 200) + await correct2.body.dump() - const invalid1 = await fastify.inject({ + const invalid1 = await request(address, { method: 'POST', url: '/', headers: { 'content-type': 'application/json ;' }, - body: { invalid: 'string' } + body: JSON.stringify({ invalid: 'string' }) }) t.equal(invalid1.statusCode, 400) - t.equal(invalid1.json().code, 'FST_ERR_VALIDATION') + t.equal((await invalid1.body.json()).code, 'FST_ERR_VALIDATION') - const invalid2 = await fastify.inject({ + const invalid2 = await request(address, { method: 'POST', url: '/', headers: { 'content-type': 'ApPlIcAtIoN/JsOn;' }, - body: { invalid: 'string' } + body: JSON.stringify({ invalid: 'string' }) }) t.equal(invalid2.statusCode, 400) - t.equal(invalid2.json().code, 'FST_ERR_VALIDATION') + t.equal((await invalid2.body.json()).code, 'FST_ERR_VALIDATION') - const invalid3 = await fastify.inject({ + const invalid3 = await request(address, { method: 'POST', url: '/', headers: { 'content-type': 'ApPlIcAtIoN/JsOn ;' }, - body: { invalid: 'string' } + body: JSON.stringify({ invalid: 'string' }) }) t.equal(invalid3.statusCode, 400) - t.equal(invalid3.json().code, 'FST_ERR_VALIDATION') + t.equal((await invalid3.body.json()).code, 'FST_ERR_VALIDATION') + + const invalid4 = await request(address, { + method: 'POST', + url: '/', + headers: { + 'content-type': 'ApPlIcAtIoN/JsOn foo;' + }, + body: JSON.stringify({ invalid: 'string' }) + }) + t.equal(invalid4.statusCode, 400) + t.equal((await invalid4.body.json()).code, 'FST_ERR_VALIDATION') })
2 files changed · +131 −1
lib/validation.js+11 −1 modified@@ -155,7 +155,7 @@ function validate (context, request, execution) { validatorFunction = context[bodySchema] } else if (context[bodySchema]) { // TODO: add request.contentType and reuse it here - const contentType = request.headers['content-type']?.split(';', 1)[0] + const contentType = getEssenceMediaType(request.headers['content-type']) const contentSchema = context[bodySchema][contentType] if (contentSchema) { validatorFunction = contentSchema @@ -254,6 +254,16 @@ function wrapValidationError (result, dataVar, schemaErrorFormatter) { return error } +/** + * simple function to retrieve the essence media type + * @param {string} header + * @returns {string} Mimetype string. + */ +function getEssenceMediaType (header) { + if (!header) return '' + return header.split(';', 1)[0].trim().toLowerCase() +} + module.exports = { symbols: { bodySchema, querystringSchema, responseSchema, paramsSchema, headersSchema }, compileSchemasForValidation,
test/schema-validation.test.js+120 −0 modified@@ -1300,3 +1300,123 @@ test('Custom validator builder override by custom validator compiler in child in }) t.equal(two.statusCode, 200) }) + +test('Schema validation when no content type is provided', async t => { + // this case should not be happened in normal use-case, + // it is added for the completeness of code branch + const fastify = Fastify() + + fastify.post('/', { + schema: { + body: { + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + foo: { type: 'string' } + }, + required: ['foo'], + additionalProperties: false + } + } + } + } + }, + preValidation: async (request) => { + request.headers['content-type'] = undefined + } + }, async () => 'ok') + + await fastify.ready() + + const invalid = await fastify.inject({ + method: 'POST', + url: '/', + headers: { + 'content-type': 'application/json' + }, + body: { invalid: 'string' } + }) + t.equal(invalid.statusCode, 200) +}) + +test('Schema validation will not be bypass by different content type', async t => { + t.plan(8) + + const fastify = Fastify() + + fastify.post('/', { + schema: { + body: { + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + foo: { type: 'string' } + }, + required: ['foo'], + additionalProperties: false + } + } + } + } + } + }, async () => 'ok') + + await fastify.ready() + + const correct1 = await fastify.inject({ + method: 'POST', + url: '/', + headers: { + 'content-type': 'application/json' + }, + body: { foo: 'string' } + }) + t.equal(correct1.statusCode, 200) + + const correct2 = await fastify.inject({ + method: 'POST', + url: '/', + headers: { + 'content-type': 'application/json; charset=utf-8' + }, + body: { foo: 'string' } + }) + t.equal(correct2.statusCode, 200) + + const invalid1 = await fastify.inject({ + method: 'POST', + url: '/', + headers: { + 'content-type': 'application/json ;' + }, + body: { invalid: 'string' } + }) + t.equal(invalid1.statusCode, 400) + t.equal(invalid1.json().code, 'FST_ERR_VALIDATION') + + const invalid2 = await fastify.inject({ + method: 'POST', + url: '/', + headers: { + 'content-type': 'ApPlIcAtIoN/JsOn;' + }, + body: { invalid: 'string' } + }) + t.equal(invalid2.statusCode, 400) + t.equal(invalid2.json().code, 'FST_ERR_VALIDATION') + + const invalid3 = await fastify.inject({ + method: 'POST', + url: '/', + headers: { + 'content-type': 'ApPlIcAtIoN/JsOn ;' + }, + body: { invalid: 'string' } + }) + t.equal(invalid3.statusCode, 400) + t.equal(invalid3.json().code, 'FST_ERR_VALIDATION') +})
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
6- github.com/advisories/GHSA-mg2h-6x62-wpwcghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-32442ghsaADVISORY
- github.com/fastify/fastify/commit/436da4c06dfbbb8c24adee3a64de0c51e4f47418ghsax_refsource_MISCWEB
- github.com/fastify/fastify/commit/f3d2bcb3963cd570a582e5d39aab01a9ae692fe4ghsax_refsource_MISCWEB
- github.com/fastify/fastify/security/advisories/GHSA-mg2h-6x62-wpwcghsax_refsource_CONFIRMWEB
- hackerone.com/reports/3087928ghsax_refsource_MISCWEB
News mentions
0No linked articles in our index yet.