Hono: Arbitrary file access via serveStatic vulnerability
Description
Hono is a Web application framework that provides support for any JavaScript runtime. Prior to version 4.12.4, when using serveStatic together with route-based middleware protections (e.g. app.use('/admin/*', ...)), inconsistent URL decoding allowed protected static resources to be accessed without authorization. The router used decodeURI, while serveStatic used decodeURIComponent. This mismatch allowed paths containing encoded slashes (%2F) to bypass middleware protections while still resolving to the intended filesystem path. This issue has been patched in version 4.12.4.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
hononpm | < 4.12.4 | 4.12.4 |
Affected products
1Patches
13 files changed · +26 −2
src/middleware/serve-static/index.test.ts+23 −0 modified@@ -267,5 +267,28 @@ describe('Serve Static Middleware', () => { const res = await app.request('///etc/passwd') expect(await res.text()).toBe('Hello in etc/passwd') }) + + it('Should not allow bypass via path mismatch between middleware and serveStatic', async () => { + const app = new Hono() + + app.use('/admin/*', async (c, next) => { + c.header('X-Authorized', 'true') + await next() + }) + + const serveStatic = baseServeStatic({ + getContent, + root: '.', + }) + app.use('/*', serveStatic) + + const res = await app.request('/admin/secret.txt') + expect(res.headers.get('X-Authorized')).toBe('true') + expect(await res.text()).toBe('Hello in admin/secret.txt') + + const res2 = await app.request('/admin%2Fsecret.txt') + expect(res2.headers.get('X-Authorized')).toBeNull() + expect(await res2.text()).toBe('Hello in admin%2Fsecret.txt') + }) }) })
src/middleware/serve-static/index.ts+2 −1 modified@@ -7,6 +7,7 @@ import type { Context, Data } from '../../context' import type { Env, MiddlewareHandler } from '../../types' import { COMPRESSIBLE_CONTENT_TYPE_REGEX } from '../../utils/compress' import { getMimeType } from '../../utils/mime' +import { tryDecodeURI } from '../../utils/url' import { defaultJoin } from './path' export type ServeStaticOptions<E extends Env = Env> = { @@ -62,7 +63,7 @@ export const serveStatic = <E extends Env = Env>( filename = options.path } else { try { - filename = decodeURIComponent(c.req.path) + filename = tryDecodeURI(c.req.path) if (/(?:^|[\/\\])\.\.(?:$|[\/\\])/.test(filename)) { throw new Error() }
src/utils/url.ts+1 −1 modified@@ -101,7 +101,7 @@ export const tryDecode = (str: string, decoder: Decoder): string => { * tryDecodeURI('Hello%20World') // 'Hello World' * tryDecodeURI('Hello%20World/%A4%A2') // 'Hello World/%A4%A2' */ -const tryDecodeURI = (str: string) => tryDecode(str, decodeURI) +export const tryDecodeURI = (str: string) => tryDecode(str, decodeURI) export const getPath = (request: Request): string => { const url = request.url
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-q5qw-h33p-qvwrghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-29045ghsaADVISORY
- github.com/honojs/hono/commit/6a0607a929d888893f0c91d92dce2fcfdb3662a3ghsax_refsource_MISCWEB
- github.com/honojs/hono/security/advisories/GHSA-q5qw-h33p-qvwrghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.