VYPR
High severityNVD Advisory· Published Apr 18, 2025· Updated Aug 22, 2025

Fastify vulnerable to invalid content-type parsing, which could lead to validation bypass

CVE-2025-32442

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.

PackageAffected versionsPatched versions
fastifynpm
>= 5.0.0, < 5.3.25.3.2
fastifynpm
>= 4.29.0, < 4.29.14.29.1

Affected products

1

Patches

2
f3d2bcb3963c

fix: treat space as a delimiter in content-type parsing (#6064)

https://github.com/fastify/fastifyMatteo CollinaApr 18, 2025via ghsa
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')
     })
    
436da4c06dfb

Merge commit from fork

https://github.com/fastify/fastifyKaKaApr 18, 2025via ghsa
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

News mentions

0

No linked articles in our index yet.