VYPR
Moderate severityNVD Advisory· Published Sep 12, 2025· Updated Sep 12, 2025

Hono has Body Limit Middleware Bypass

CVE-2025-59139

Description

Hono is a Web application framework that provides support for any JavaScript runtime. In versions prior to 4.9.7, a flaw in the bodyLimit middleware could allow bypassing the configured request body size limit when conflicting HTTP headers were present. The middleware previously prioritized the Content-Length header even when a Transfer-Encoding: chunked header was also included. According to the HTTP specification, Content-Length must be ignored in such cases. This discrepancy could allow oversized request bodies to bypass the configured limit. Most standards-compliant runtimes and reverse proxies may reject such malformed requests with 400 Bad Request, so the practical impact depends on the runtime and deployment environment. If body size limits are used as a safeguard against large or malicious requests, this flaw could allow attackers to send oversized request bodies. The primary risk is denial of service (DoS) due to excessive memory or CPU consumption when handling very large requests. The implementation has been updated to align with the HTTP specification, ensuring that Transfer-Encoding takes precedence over Content-Length. The issue is fixed in Hono v4.9.7, and all users should upgrade immediately.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
hononpm
< 4.9.74.9.7

Affected products

1

Patches

1
605c70560b52

Merge commit from fork

https://github.com/honojs/honoYusuke WadaSep 12, 2025via ghsa
2 files changed · +81 3
  • src/middleware/body-limit/index.test.ts+68 0 modified
    @@ -130,4 +130,72 @@ describe('Body Limit Middleware', () => {
           expect(await res.text()).toBe('no')
         })
       })
    +
    +  describe('Transfer-Encoding and Content-Length headers', () => {
    +    beforeEach(() => {
    +      app = new Hono()
    +      app.use('*', bodyLimit({ maxSize: 10 }))
    +      app.post('/test', async (c) => {
    +        return c.text(await c.req.text())
    +      })
    +    })
    +
    +    it('should prioritize Transfer-Encoding over Content-Length', async () => {
    +      // Create a chunked body that exceeds the limit
    +      const largeContent = 'this is a large content that exceeds 10 bytes'
    +      const chunks = [largeContent.slice(0, 20), largeContent.slice(20)]
    +
    +      const stream = new ReadableStream({
    +        start(controller) {
    +          chunks.forEach((chunk) => {
    +            controller.enqueue(new TextEncoder().encode(chunk))
    +          })
    +          controller.close()
    +        },
    +      })
    +
    +      const res = await app.request('/test', {
    +        method: 'POST',
    +        headers: {
    +          'Content-Length': '5', // Small content-length (bypass attempt)
    +          'Transfer-Encoding': 'chunked', // But chunked encoding with large content
    +        },
    +        body: stream,
    +        duplex: 'half',
    +      } as RequestInit)
    +
    +      // Should reject based on actual chunked content size, not Content-Length
    +      expect(res.status).toBe(413)
    +    })
    +
    +    it('should handle only Content-Length header correctly', async () => {
    +      const smallContent = 'small'
    +      const res = await app.request('/test', buildRequestInit({ body: smallContent }))
    +
    +      expect(res.status).toBe(200)
    +      expect(await res.text()).toBe(smallContent)
    +    })
    +
    +    it('should handle only Transfer-Encoding header correctly', async () => {
    +      const content = 'test'
    +      const stream = new ReadableStream({
    +        start(controller) {
    +          controller.enqueue(new TextEncoder().encode(content))
    +          controller.close()
    +        },
    +      })
    +
    +      const res = await app.request('/test', {
    +        method: 'POST',
    +        headers: {
    +          'Transfer-Encoding': 'chunked',
    +        },
    +        body: stream,
    +        duplex: 'half',
    +      } as RequestInit)
    +
    +      expect(res.status).toBe(200)
    +      expect(await res.text()).toBe(content)
    +    })
    +  })
     })
    
  • src/middleware/body-limit/index.ts+13 3 modified
    @@ -71,13 +71,23 @@ export const bodyLimit = (options: BodyLimitOptions): MiddlewareHandler => {
           return next()
         }
     
    -    if (c.req.raw.headers.has('content-length')) {
    -      // we can trust content-length header because it's already validated by server
    +    const hasTransferEncoding = c.req.raw.headers.has('transfer-encoding')
    +    const hasContentLength = c.req.raw.headers.has('content-length')
    +
    +    // RFC 7230: If both Transfer-Encoding and Content-Length are present,
    +    // Transfer-Encoding takes precedence and Content-Length should be ignored
    +    if (hasTransferEncoding && hasContentLength) {
    +      // Both headers present - follow RFC 7230 and ignore Content-Length
    +      // This might indicate request smuggling attempt
    +    }
    +
    +    if (hasContentLength && !hasTransferEncoding) {
    +      // Only Content-Length present - we can trust it
           const contentLength = parseInt(c.req.raw.headers.get('content-length') || '0', 10)
           return contentLength > maxSize ? onError(c) : next()
         }
     
    -    // maybe chunked transfer encoding
    +    // Transfer-Encoding present (chunked) or no length headers
     
         let size = 0
         const rawReader = c.req.raw.body.getReader()
    

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

4

News mentions

0

No linked articles in our index yet.