CVE-2026-29087
Description
@hono/node-server allows running the Hono application on Node.js. Prior to version 1.19.10, when using @hono/node-server's static file serving together with route-based middleware protections (e.g. protecting /admin/*), inconsistent URL decoding can allow protected static resources to be accessed without authorization. In particular, paths containing encoded slashes (%2F) may be evaluated differently by routing/middleware matching versus static file path resolution, enabling a bypass where middleware does not run but the static file is still served. This issue has been patched in version 1.19.10.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
@hono/node-servernpm | < 1.19.10 | 1.19.10 |
Affected products
1Patches
1455015be1697Merge commit from fork
3 files changed · +45 −2
src/serve-static.ts+20 −1 modified@@ -68,6 +68,25 @@ const getStats = (path: string) => { return stats } +type Decoder = (str: string) => string + +const tryDecode = (str: string, decoder: Decoder): string => { + try { + return decoder(str) + } catch { + // Decode only valid %xx sequences in chunks; keep undecodable parts as-is + return str.replace(/(?:%[0-9A-Fa-f]{2})+/g, (match) => { + try { + return decoder(match) + } catch { + return match + } + }) + } +} + +const tryDecodeURI = (str: string) => tryDecode(str, decodeURI) + // eslint-disable-next-line @typescript-eslint/no-explicit-any export const serveStatic = <E extends Env = any>( options: ServeStaticOptions<E> = { root: '' } @@ -91,7 +110,7 @@ export const serveStatic = <E extends Env = any>( filename = optionPath } else { try { - filename = decodeURIComponent(c.req.path) + filename = tryDecodeURI(c.req.path) if (/(?:^|[\/\\])\.\.(?:$|[\/\\])/.test(filename)) { throw new Error() }
test/assets/static/admin/secret.txt+1 −0 added@@ -0,0 +1 @@ +secret \ No newline at end of file
test/serve-static.test.ts+24 −1 modified@@ -330,7 +330,7 @@ describe('Serve Static Middleware', () => { }) }) - describe('Security tests', () => { + describe('Path traversal security tests', () => { const app = new Hono() const server = createAdaptorServer(app) app.use('/static/*', serveStatic({ root: './test/assets' })) @@ -361,6 +361,29 @@ describe('Serve Static Middleware', () => { }) }) + describe('Path mismatch security tests', () => { + const app = new Hono() + const server = createAdaptorServer(app) + + app.use('/static/admin/*', async (c, next) => { + c.header('X-Authorized', 'true') + await next() + }) + + app.use('/static/*', serveStatic({ root: './test/assets' })) + + it('Should not allow bypass via path mismatch between middleware and serveStatic', async () => { + const res = await request(server).get('/static/admin/secret.txt') + expect(res.headers['x-authorized']).toBe('true') + expect(res.text).toBe('secret') + + const res2 = await request(server).get('/static/admin%2Fsecret.txt') + expect(res2.status).toBe(404) + expect(res2.headers['x-authorized']).toBeUndefined() + expect(res2.text).not.toBe('secret') + }) + }) + describe('Stream error handling', () => { const testFile = path.join(__dirname, 'assets', 'static', 'plain.txt') console.log(testFile)
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
4News mentions
0No linked articles in our index yet.