VYPR
High severityNVD Advisory· Published Mar 4, 2026· Updated Mar 5, 2026

Hono: Arbitrary file access via serveStatic vulnerability

CVE-2026-29045

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.

PackageAffected versionsPatched versions
hononpm
< 4.12.44.12.4

Affected products

1

Patches

1
6a0607a929d8

Merge commit from fork

https://github.com/honojs/honotechfishMar 3, 2026via ghsa
3 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

News mentions

0

No linked articles in our index yet.