VYPR
Low severityNVD Advisory· Published Feb 3, 2026· Updated Feb 4, 2026

Fastify Vulnerable to DoS via Unbounded Memory Allocation in sendWebStream

CVE-2026-25224

Description

Fastify is a fast and low overhead web framework, for Node.js. Prior to version 5.7.3, a denial-of-service vulnerability in Fastify’s Web Streams response handling can allow a remote client to exhaust server memory. Applications that return a ReadableStream (or Response with a Web Stream body) via reply.send() are impacted. A slow or non-reading client can trigger unbounded buffering when backpressure is ignored, leading to process crashes or severe degradation. This issue has been patched in version 5.7.3.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
fastifynpm
< 5.7.35.7.3

Affected products

1

Patches

1
eb11156396f6

Merge commit from fork

https://github.com/fastify/fastifyMatteo CollinaFeb 2, 2026via ghsa
2 files changed · +88 1
  • lib/reply.js+15 1 modified
    @@ -687,6 +687,7 @@ function sendWebStream (payload, res, reply) {
     
       let sourceOpen = true
       let errorLogged = false
    +  let waitingDrain = false
       const reader = payload.getReader()
     
       eos(res, function (err) {
    @@ -719,7 +720,20 @@ function sendWebStream (payload, res, reply) {
           reader.cancel().catch(noop)
           return
         }
    -    res.write(result.value)
    +    const shouldContinue = res.write(result.value)
    +    if (shouldContinue === false) {
    +      waitingDrain = true
    +      res.once('drain', onDrain)
    +      return
    +    }
    +    reader.read().then(onRead, onReadError)
    +  }
    +
    +  function onDrain () {
    +    if (!waitingDrain || !sourceOpen || res.destroyed) {
    +      return
    +    }
    +    waitingDrain = false
         reader.read().then(onRead, onReadError)
       }
     
    
  • test/web-api.test.js+73 0 modified
    @@ -6,6 +6,7 @@ const fs = require('node:fs')
     const { Readable } = require('node:stream')
     const { fetch: undiciFetch } = require('undici')
     const http = require('node:http')
    +const { setTimeout: sleep } = require('node:timers/promises')
     
     test('should response with a ReadableStream', async (t) => {
       t.plan(2)
    @@ -427,6 +428,78 @@ test('WebStream should cancel reader when response is destroyed', (t, done) => {
       })
     })
     
    +test('WebStream should respect backpressure', async (t) => {
    +  t.plan(3)
    +
    +  const fastify = Fastify()
    +  t.after(() => fastify.close())
    +
    +  let drainEmittedAt = 0
    +  let secondWriteAt = 0
    +  let resolveSecondWrite
    +  const secondWrite = new Promise((resolve) => {
    +    resolveSecondWrite = resolve
    +  })
    +
    +  fastify.get('/', function (request, reply) {
    +    const raw = reply.raw
    +    const originalWrite = raw.write.bind(raw)
    +    const bufferedChunks = []
    +    let wroteFirstChunk = false
    +
    +    raw.once('drain', () => {
    +      for (const bufferedChunk of bufferedChunks) {
    +        originalWrite(bufferedChunk)
    +      }
    +    })
    +
    +    raw.write = function (chunk, encoding, cb) {
    +      if (!wroteFirstChunk) {
    +        wroteFirstChunk = true
    +        bufferedChunks.push(Buffer.from(chunk))
    +        sleep(100).then(() => {
    +          drainEmittedAt = Date.now()
    +          raw.emit('drain')
    +        })
    +        if (typeof cb === 'function') {
    +          cb()
    +        }
    +        return false
    +      }
    +      if (!secondWriteAt) {
    +        secondWriteAt = Date.now()
    +        resolveSecondWrite()
    +      }
    +      return originalWrite(chunk, encoding, cb)
    +    }
    +
    +    const stream = new ReadableStream({
    +      start (controller) {
    +        controller.enqueue(Buffer.from('chunk-1'))
    +      },
    +      pull (controller) {
    +        controller.enqueue(Buffer.from('chunk-2'))
    +        controller.close()
    +      }
    +    })
    +
    +    reply.header('content-type', 'text/plain').send(stream)
    +  })
    +
    +  await fastify.listen({ port: 0 })
    +
    +  const response = await undiciFetch(`http://localhost:${fastify.server.address().port}/`)
    +  const bodyPromise = response.text()
    +
    +  await secondWrite
    +  await sleep(120)
    +  const body = await bodyPromise
    +
    +  t.assert.strictEqual(response.status, 200)
    +  t.assert.strictEqual(body, 'chunk-1chunk-2')
    +  t.assert.ok(secondWriteAt >= drainEmittedAt)
    +})
    +
     test('WebStream should warn when headers already sent', async (t) => {
       t.plan(2)
     
    

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

5

News mentions

0

No linked articles in our index yet.