Medium severity4.8NVD Advisory· Published Apr 8, 2026· Updated Apr 21, 2026
CVE-2026-39410
CVE-2026-39410
Description
Hono is a Web application framework that provides support for any JavaScript runtime. Prior to 4.12.12, a discrepancy between browser cookie parsing and parse() handling allows cookie prefix protections to be bypassed. Cookie names that are treated as distinct by the browser may be normalized to the same key by parse(), allowing attacker-controlled cookies to override legitimate ones. This vulnerability is fixed in 4.12.12.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
hononpm | < 4.12.12 | 4.12.12 |
Affected products
1Patches
12 files changed · +55 −5
src/utils/cookie.test.ts+28 −0 modified@@ -9,6 +9,13 @@ describe('Parse cookie', () => { expect(cookie['tasty_cookie']).toBe('strawberry') }) + it('Should trim only SP and HTAB around cookie pairs', () => { + const cookieString = '\tyummy_cookie=choco;\t tasty_cookie = strawberry \t' + const cookie: Cookie = parse(cookieString) + expect(cookie['yummy_cookie']).toBe('choco') + expect(cookie['tasty_cookie']).toBe('strawberry') + }) + it('Should parse quoted cookie values', () => { const cookieString = 'yummy_cookie="choco"; tasty_cookie = " strawberry " ; best_cookie="%20sugar%20";' @@ -81,6 +88,19 @@ describe('Parse cookie', () => { expect(cookie['best_cookie\\']).toBeUndefined() }) + it('Should ignore NBSP-prefixed cookie names when parsing one cookie by name', () => { + const cookieString = '\u00a0dummy-cookie=evil; dummy-cookie=victim' + const cookie: Cookie = parse(cookieString, 'dummy-cookie') + expect(cookie['dummy-cookie']).toBe('victim') + }) + + it('Should not collapse NBSP-prefixed cookie names when parsing all cookies', () => { + const cookieString = 'dummy-cookie=victim; \u00a0dummy-cookie=evil' + const cookie: Cookie = parse(cookieString) + expect(cookie['dummy-cookie']).toBe('victim') + expect(cookie['\u00a0dummy-cookie']).toBeUndefined() + }) + it('Should parse signed cookies', async () => { const secret = 'secret ingredient' const cookieString = @@ -159,6 +179,14 @@ describe('Parse cookie', () => { expect(cookie['tasty_cookie']).toBe('strawberry') expect(cookie['great_cookie']).toBeUndefined() }) + + it('Should ignore NBSP-prefixed signed cookie names when parsing one cookie by name', async () => { + const secret = 'secret ingredient' + const cookieString = + '\u00a0dummy-cookie=evil.UdFR2rBpS1GsHfGlUiYyMIdqxqwuEgplyQIgTJgpGWY%3D; dummy-cookie=choco.UdFR2rBpS1GsHfGlUiYyMIdqxqwuEgplyQIgTJgpGWY%3D' + const cookie: SignedCookie = await parseSigned(cookieString, secret, 'dummy-cookie') + expect(cookie['dummy-cookie']).toBe('choco') + }) }) describe('Set cookie', () => {
src/utils/cookie.ts+27 −5 modified@@ -76,26 +76,48 @@ const validCookieNameRegEx = /^[\w!#$%&'*.^`|~+-]+$/ // (see: https://github.com/golang/go/issues/7243) const validCookieValueRegEx = /^[ !#-:<-[\]-~]*$/ +const trimCookieWhitespace = (value: string): string => { + let start = 0 + let end = value.length + + while (start < end) { + const charCode = value.charCodeAt(start) + if (charCode !== 0x20 && charCode !== 0x09) { + break + } + start++ + } + + while (end > start) { + const charCode = value.charCodeAt(end - 1) + if (charCode !== 0x20 && charCode !== 0x09) { + break + } + end-- + } + + return start === 0 && end === value.length ? value : value.slice(start, end) +} + export const parse = (cookie: string, name?: string): Cookie => { if (name && cookie.indexOf(name) === -1) { // Fast-path: return immediately if the demanded-key is not in the cookie string return {} } - const pairs = cookie.trim().split(';') + const pairs = cookie.split(';') const parsedCookie: Cookie = {} - for (let pairStr of pairs) { - pairStr = pairStr.trim() + for (const pairStr of pairs) { const valueStartPos = pairStr.indexOf('=') if (valueStartPos === -1) { continue } - const cookieName = pairStr.substring(0, valueStartPos).trim() + const cookieName = trimCookieWhitespace(pairStr.substring(0, valueStartPos)) if ((name && name !== cookieName) || !validCookieNameRegEx.test(cookieName)) { continue } - let cookieValue = pairStr.substring(valueStartPos + 1).trim() + let cookieValue = trimCookieWhitespace(pairStr.substring(valueStartPos + 1)) if (cookieValue.startsWith('"') && cookieValue.endsWith('"')) { cookieValue = cookieValue.slice(1, -1) }
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-r5rp-j6wh-rvv4ghsaADVISORY
- github.com/honojs/hono/commit/cc067c85592415cb1880ad3c61ed923472452ec0nvdThird Party AdvisoryWEB
- github.com/honojs/hono/security/advisories/GHSA-r5rp-j6wh-rvv4nvdVendor AdvisoryWEB
- nvd.nist.gov/vuln/detail/CVE-2026-39410ghsaADVISORY
- github.com/honojs/hono/releases/tag/v4.12.12nvdRelease NotesWEB
News mentions
0No linked articles in our index yet.