Hono's named path parameters can be overridden in TrieRouter
Description
Hono is a web framework written in TypeScript. Prior to version 3.11.7, clients may override named path parameter values from previous requests if the application is using TrieRouter. So, there is a risk that a privileged user may use unintended parameters when deleting REST API resources. TrieRouter is used either explicitly or when the application matches a pattern that is not supported by the default RegExpRouter. Version 3.11.7 includes the change to fix this issue. As a workaround, avoid using TrieRouter directly.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
hononpm | < 3.11.7 | 3.11.7 |
Affected products
1Patches
18e2b6b085189Merge pull request from GHSA-f6gv-hh8j-q8vq
3 files changed · +49 −24
deno_dist/router/trie-router/node.ts+17 −12 modified@@ -5,12 +5,15 @@ import { splitPath, splitRoutingPath, getPattern } from '../../utils/url.ts' type HandlerSet<T> = { handler: T - params: Record<string, string> possibleKeys: string[] score: number name: string // For debug } +type HandlerParamsSet<T> = HandlerSet<T> & { + params: Record<string, string> +} + export class Node<T> { methods: Record<string, HandlerSet<T>>[] @@ -26,7 +29,7 @@ export class Node<T> { this.name = '' if (method && handler) { const m: Record<string, HandlerSet<T>> = {} - m[method] = { handler, params: {}, possibleKeys: [], score: 0, name: this.name } + m[method] = { handler, possibleKeys: [], score: 0, name: this.name } this.methods = [m] } this.patterns = [] @@ -74,7 +77,6 @@ export class Node<T> { const handlerSet: HandlerSet<T> = { handler, - params: {}, possibleKeys, name: this.name, score: this.order, @@ -87,12 +89,17 @@ export class Node<T> { } // getHandlerSets - private gHSets(node: Node<T>, method: string, params: Record<string, string>): HandlerSet<T>[] { - const handlerSets: HandlerSet<T>[] = [] + private gHSets( + node: Node<T>, + method: string, + params: Record<string, string> + ): HandlerParamsSet<T>[] { + const handlerSets: HandlerParamsSet<T>[] = [] for (let i = 0, len = node.methods.length; i < len; i++) { const m = node.methods[i] - const handlerSet = m[method] || m[METHOD_NAME_ALL] + const handlerSet = (m[method] || m[METHOD_NAME_ALL]) as HandlerParamsSet<T> if (handlerSet !== undefined) { + handlerSet.params = {} handlerSet.possibleKeys.map((key) => { handlerSet.params[key] = params[key] }) @@ -103,7 +110,7 @@ export class Node<T> { } search(method: string, path: string): [[T, Params][]] { - const handlerSets: HandlerSet<T>[] = [] + const handlerSets: HandlerParamsSet<T>[] = [] const params: Record<string, string> = {} this.params = {} @@ -126,11 +133,9 @@ export class Node<T> { if (isLast === true) { // '/hello/*' => match '/hello' if (nextNode.children['*']) { - handlerSets.push( - ...this.gHSets(nextNode.children['*'], method, { ...params, ...node.params }) - ) + handlerSets.push(...this.gHSets(nextNode.children['*'], method, node.params)) } - handlerSets.push(...this.gHSets(nextNode, method, { ...params, ...node.params })) + handlerSets.push(...this.gHSets(nextNode, method, node.params)) } else { tempNodes.push(nextNode) } @@ -144,7 +149,7 @@ export class Node<T> { if (pattern === '*') { const astNode = node.children['*'] if (astNode) { - handlerSets.push(...this.gHSets(astNode, method, { ...params, ...node.params })) + handlerSets.push(...this.gHSets(astNode, method, node.params)) tempNodes.push(astNode) } continue
src/router/trie-router/node.test.ts+15 −0 modified@@ -89,6 +89,21 @@ describe('Name path', () => { expect(res[0][0]).toEqual('get events') expect(res[0][1]['location']).toBe('yokohama') }) + + it('Should not return a previous param value', () => { + const node = new Node() + node.insert('delete', '/resource/:id', 'resource') + const [resA] = node.search('delete', '/resource/a') + const [resB] = node.search('delete', '/resource/b') + expect(resA).not.toBeNull() + expect(resA.length).toBe(1) + expect(resA[0][0]).toEqual('resource') + expect(resA[0][1]).toEqual({ id: 'a' }) + expect(resB).not.toBeNull() + expect(resB.length).toBe(1) + expect(resB[0][0]).toEqual('resource') + expect(resB[0][1]).toEqual({ id: 'b' }) + }) }) describe('Name path - Multiple route', () => {
src/router/trie-router/node.ts+17 −12 modified@@ -5,12 +5,15 @@ import { splitPath, splitRoutingPath, getPattern } from '../../utils/url' type HandlerSet<T> = { handler: T - params: Record<string, string> possibleKeys: string[] score: number name: string // For debug } +type HandlerParamsSet<T> = HandlerSet<T> & { + params: Record<string, string> +} + export class Node<T> { methods: Record<string, HandlerSet<T>>[] @@ -26,7 +29,7 @@ export class Node<T> { this.name = '' if (method && handler) { const m: Record<string, HandlerSet<T>> = {} - m[method] = { handler, params: {}, possibleKeys: [], score: 0, name: this.name } + m[method] = { handler, possibleKeys: [], score: 0, name: this.name } this.methods = [m] } this.patterns = [] @@ -74,7 +77,6 @@ export class Node<T> { const handlerSet: HandlerSet<T> = { handler, - params: {}, possibleKeys, name: this.name, score: this.order, @@ -87,12 +89,17 @@ export class Node<T> { } // getHandlerSets - private gHSets(node: Node<T>, method: string, params: Record<string, string>): HandlerSet<T>[] { - const handlerSets: HandlerSet<T>[] = [] + private gHSets( + node: Node<T>, + method: string, + params: Record<string, string> + ): HandlerParamsSet<T>[] { + const handlerSets: HandlerParamsSet<T>[] = [] for (let i = 0, len = node.methods.length; i < len; i++) { const m = node.methods[i] - const handlerSet = m[method] || m[METHOD_NAME_ALL] + const handlerSet = (m[method] || m[METHOD_NAME_ALL]) as HandlerParamsSet<T> if (handlerSet !== undefined) { + handlerSet.params = {} handlerSet.possibleKeys.map((key) => { handlerSet.params[key] = params[key] }) @@ -103,7 +110,7 @@ export class Node<T> { } search(method: string, path: string): [[T, Params][]] { - const handlerSets: HandlerSet<T>[] = [] + const handlerSets: HandlerParamsSet<T>[] = [] const params: Record<string, string> = {} this.params = {} @@ -126,11 +133,9 @@ export class Node<T> { if (isLast === true) { // '/hello/*' => match '/hello' if (nextNode.children['*']) { - handlerSets.push( - ...this.gHSets(nextNode.children['*'], method, { ...params, ...node.params }) - ) + handlerSets.push(...this.gHSets(nextNode.children['*'], method, node.params)) } - handlerSets.push(...this.gHSets(nextNode, method, { ...params, ...node.params })) + handlerSets.push(...this.gHSets(nextNode, method, node.params)) } else { tempNodes.push(nextNode) } @@ -144,7 +149,7 @@ export class Node<T> { if (pattern === '*') { const astNode = node.children['*'] if (astNode) { - handlerSets.push(...this.gHSets(astNode, method, { ...params, ...node.params })) + handlerSets.push(...this.gHSets(astNode, method, node.params)) tempNodes.push(astNode) } continue
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
5- github.com/advisories/GHSA-f6gv-hh8j-q8vqghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2023-50710ghsaADVISORY
- github.com/honojs/hono/commit/8e2b6b08518998783f66d31db4f21b1b1eecc4c8ghsax_refsource_MISCWEB
- github.com/honojs/hono/releases/tag/v3.11.7ghsax_refsource_MISCWEB
- github.com/honojs/hono/security/advisories/GHSA-f6gv-hh8j-q8vqghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.