VYPR
Moderate severityNVD Advisory· Published Dec 14, 2023· Updated Aug 2, 2024

Hono's named path parameters can be overridden in TrieRouter

CVE-2023-50710

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.

PackageAffected versionsPatched versions
hononpm
< 3.11.73.11.7

Affected products

1

Patches

1
8e2b6b085189

Merge pull request from GHSA-f6gv-hh8j-q8vq

https://github.com/honojs/honoYusuke WadaDec 14, 2023via ghsa
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

News mentions

0

No linked articles in our index yet.