Hono has Body Limit Middleware Bypass
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.
| Package | Affected versions | Patched versions |
|---|---|---|
hononpm | < 4.9.7 | 4.9.7 |
Affected products
1Patches
12 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- github.com/advisories/GHSA-92vj-g62v-jqhhghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-59139ghsaADVISORY
- github.com/honojs/hono/commit/605c70560b52f13af10379f79b76717042fafe8dghsax_refsource_MISCWEB
- github.com/honojs/hono/security/advisories/GHSA-92vj-g62v-jqhhghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.