@oomfware/safe-idn #
detect IDN homograph spoofing.
npm install @oomfware/safe-idn
browsers and other user agents must decide whether to display internationalized domain names (IDN)
in their decoded Unicode form or keep them as punycode. showing Unicode unconditionally enables
phishing — for example, xn--80ak6aa92e.com decodes to аррӏе.com, which uses Cyrillic letters to
impersonate apple.com.
this library implements the per-label checks from Chromium's IDN display algorithm in TypeScript. it checks each label of a domain for script mixing, confusable characters, dangerous patterns, and other spoofing vectors, then returns either the safe Unicode form or the original punycode.
it deliberately omits Chromium's skeleton matching against a list of popular domains. that approach
requires bundling and maintaining an inherently incomplete brand list, so — like Firefox — this
library relies on structural rules that protect every domain uniformly rather than a curated set.
the whole-script confusable check still catches the dangerous invisible cases (e.g. all-Cyrillic
аррӏе), so this remains stricter than Firefox on those. the trade-off is that same-script
lookalikes of a specific brand (e.g. googlé.com) are treated as valid Unicode.
usage #
quick check #
use safeDisplay() to get the safe display form of a domain:
import { safeDisplay } from '@oomfware/safe-idn';
// safe domains decode to Unicode
safeDisplay('xn--nxasmq6b.com');
// -> "βόλος.com"
// spoofed domains stay as punycode
safeDisplay('xn--80ak6aa92e.com');
// -> "xn--80ak6aa92e.com" (Cyrillic "аррӏе" impersonating "apple")
detailed results #
use checkDomain() for per-label verdicts:
import { checkDomain } from '@oomfware/safe-idn';
const result = checkDomain('xn--80ak6aa92e.com');
console.log(result.display);
// -> "xn--80ak6aa92e.com"
console.log(result.labels[0]);
// -> { input: 'xn--80ak6aa92e', unicode: 'аррӏе', result: 'unsafe' }
each label result contains:
input— the original label as it appeared in the domainunicode— the decoded Unicode form (even if unsafe)result—'safe','unsafe', or'invalid'
what it checks #
the following per-label safety checks are performed, matching Chromium's behavior:
- script mixing — blocks unsafe combinations of Unicode scripts (e.g., Latin + Cyrillic)
- whole-script confusables — detects labels where every character in a script has a Latin lookalike (e.g., Cyrillic "а" for Latin "a")
- character blocklist — blocks characters known to cause confusion (symbols, ligatures, IPA extensions, etc.)
- dangerous patterns — catches combining mark abuse, dot-after-i/j tricks, and RTL mark misplacement
- mixed digit systems — prevents mixing digits from different scripts
- digit lookalikes — detects non-digit characters that resemble ASCII digits
- kana confusables — catches Hiragana/Katakana interchange and context violations
- TLD-specific rules — restricts characters like þ, ð, ə, and · to their appropriate TLDs
- deviation characters — blocks ZWNJ and ZWJ