Skip to content

[GHSA-xq7p-g2vc-g82p] Homograph attack allows Unicode lookalike characters to bypass validation.#7466

Open
Wenxin-Jiang wants to merge 1 commit intoWenxin-Jiang/advisory-improvement-7466from
Wenxin-Jiang-GHSA-xq7p-g2vc-g82p
Open

[GHSA-xq7p-g2vc-g82p] Homograph attack allows Unicode lookalike characters to bypass validation.#7466
Wenxin-Jiang wants to merge 1 commit intoWenxin-Jiang/advisory-improvement-7466from
Wenxin-Jiang-GHSA-xq7p-g2vc-g82p

Conversation

@Wenxin-Jiang
Copy link
Copy Markdown

Updates

  • Affected products

Comments
CVE-2025-27611 is exploitable through the decodeUnsafe inner loop, which uses a Uint8Array(256) lookup table indexed by charCodeAt(). When the input contains a Unicode homoglyph whose code point exceeds 255, the typed-array access returns undefined. Because undefined !== 255, the sentinel check if (carry === 255) return is bypassed. From there, undefined + number yields NaN, NaN >>> 0 becomes 0, and the invalid character is silently interpreted as the first alphabet symbol. The fix in 3.0.11 adds an explicit if (charCode > 255) return guard before the lookup; the same fix was later released in 4.0.1 and 5.0.1.

This vulnerable implementation was introduced in 3.0.5 as part of a performance rewrite. Every earlier release—the entire 1.x line, the entire 2.x line, and 3.0.0 through 3.0.4—uses a plain JavaScript object for alphabet lookup:

// 1.x, 2.x, and 3.0.0–3.0.4
var ALPHABET_MAP = {}
for (var i = 0; i < ALPHABET.length; i++) {
  ALPHABET_MAP[ALPHABET.charAt(i)] = i
}
// ...
var value = ALPHABET_MAP[string[i]]
if (value === undefined) throw new Error('Non-base' + BASE + ' character')  // or: return

In those versions, ALPHABET_MAP is keyed by the actual character string rather than by numeric character code. A Unicode homoglyph lookup therefore returns undefined, and the existing === undefined check correctly rejects it. There is no out-of-bounds read, no sentinel bypass, and no silent reinterpretation of the invalid character as a valid digit. A homograph PoC against any pre-3.0.5 release stops at that validation check and does not reach a vulnerable state.

The vulnerability first appears in the 3.0.4 -> 3.0.5 rewrite, which replaces the object-based map with BASE_MAP = new Uint8Array(256) and changes the invalid-character sentinel from undefined to 255. That optimization introduces the vulnerable shape by combining charCodeAt() indexing with a fixed-size typed array and an incomplete bounds check.

Evidence

  • npm registry tarballs inspected: 1.0.0, 1.0.4, 1.1.0, 2.0.6, 3.0.0, 3.0.1, 3.0.2, 3.0.3, 3.0.4, 3.0.5, 3.0.6, 3.0.7, 3.0.8, 3.0.9, 3.0.10, 3.0.11, 4.0.0, 4.0.1, 5.0.0, 5.0.1
  • Grep across each version’s decode implementation for ALPHABET_MAP, BASE_MAP, new Uint8Array(256), and charCodeAt() shows the implementation cutover at 3.0.4 -> 3.0.5
  • 3.0.4 decode path uses ALPHABET_MAP[string[i]] followed by if (value === undefined) return, so no out-of-bounds typed-array access is possible
  • 3.0.5 decode path uses BASE_MAP[source.charCodeAt(psz)] followed by if (carry === 255) return, which is the vulnerable pattern
  • 3.0.11 adds if (charCode > 255) return before the BASE_MAP lookup; equivalent fixes appear in 4.0.1 and 5.0.1

Conclusion

The correct introduced version is 3.0.5, not any earlier release. Versions prior to 3.0.5 use a character-keyed object map that correctly rejects Unicode homoglyph input and do not contain the typed-array lookup pattern required for exploitation.

@github
Copy link
Copy Markdown
Collaborator

github commented Apr 20, 2026

Hi there @junderw! A community member has suggested an improvement to your security advisory. If approved, this change will affect the global advisory listed at github.com/advisories. It will not affect the version listed in your project repository.

This change will be reviewed by our Security Curation Team. If you have thoughts or feedback, please share them in a comment here! If this PR has already been closed, you can start a new community contribution for this advisory

@github-actions github-actions Bot changed the base branch from main to Wenxin-Jiang/advisory-improvement-7466 April 20, 2026 16:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants