@ipfy/homoglyph
v1.0.0
Published
Detect visually confusable domain names using Unicode UTS #39.
Maintainers
Readme
Homoglyph
Unicode-aware detection of visually confusable domain names.
Homoglyph is a multi-language, standards-based library for spotting Unicode homoglyph / homograph structure in domain names, using Unicode UTS #39 17.0.0.
paypal.com
раypal.comThose strings are not the same domain. The second mixes Cyrillic letters that UTS #39 maps as confusable with Latin.
It is for developers who accept domains through email fields, URL fields, registration forms, and similar inputs.
Implementations
One GitHub repository, ipfy/Homoglyph, ships native packages:
| Language | Package | Install |
| --- | --- | --- |
| JavaScript | npm @ipfy/homoglyph | npm install @ipfy/homoglyph |
| Python | PyPI ipfy-homoglyph | pip install ipfy-homoglyph |
| PHP | Packagist ipfy/homoglyph | composer require ipfy/homoglyph |
All three implement the same Homoglyph specification (see docs/DESIGN.md and docs/ARCHITECTURE.md), the same Unicode 17.0.0 confusable data, and the shared corpus in conformance/cases/.
JavaScript additionally provides browser warn() / reject() helpers. Python and PHP provide the core detect() API only.
What it is — and is not
The security boundary is the domain. Email local-parts and URL paths, queries, and fragments are not treated as hostname evidence.
| Observation | Meaning |
| --- | --- |
| Unicode in a domain | Not malicious. IDNs are legitimate. |
| Non-Latin | Not malicious. |
| Punycode / xn-- | Not malicious. Valid A-labels are decoded, then analyzed. |
| Mixed script | Not automatically malicious. |
| detected: true | A UTS #39-relevant issue worth reporting |
| rejectable: true | High-confidence finding suitable for hard enforcement |
Homoglyph does not decide whether a domain is a phishing site. It is not a blacklist, reputation service, DNS client, or TLD policy engine.
JavaScript (npm)
npm install @ipfy/homoglyphimport Homoglyph from '@ipfy/homoglyph';
const result = Homoglyph.detect('раypal.com');
if (result.detected) {
console.log(result.reason, result.domain, result.rejectable);
}CDN (IIFE, globalThis.Homoglyph):
<script src="https://cdn.jsdelivr.net/npm/@ipfy/homoglyph/dist/homoglyph.min.js"></script>Browser-only helpers: Homoglyph.warn(input) and Homoglyph.reject(input). See below. There is no init().
Python (PyPI)
pip install ipfy-homoglyphimport homoglyph
result = homoglyph.detect("раypal.com")
if result.detected:
print(result.reason, result.domain, result.rejectable)PHP (Packagist)
Requires PHP 8.1+ and ext-intl (Unicode NFC/NFD via Normalizer).
composer require ipfy/homoglyphuse IPfy\Homoglyph\Homoglyph;
$result = Homoglyph::detect('раypal.com');
if ($result->detected) {
echo $result->reason, ' ', $result->domain;
}Email and URL behavior
user@раypal.com → analyze раypal.com
mаnο[email protected] → domain paypal.com; not rejectable
https://раypal.com/login → analyze раypal.com
https://example.com/раypal → path ignored
https://example.com/?q=раypal → query ignored
https://example.com/#раypal → fragment ignored
пример.com → labels analyzed independently (not mixed-script)Punycode / IDNA
Valid xn-- A-labels are decoded and then analyzed. Punycode itself is not evidence of malice. Successful Punycode decoding is not full IDNA validation.
Unicode
| Item | Value |
| --- | --- |
| Unicode / UTS #39 | 17.0.0 |
| Source of truth | unicode/ in this repository |
| Runtime download | none |
Refresh generated maps with npm run generate (writes JavaScript, Python, and PHP runtime data from the same files).
JavaScript warn() / reject()
These exist only in the JavaScript browser build. They attach form metadata or constraint validation from detect(). Visitor-facing reject() messages are ordinary validation text, not “phishing” warnings.
Development
npm install
npm run generate
npm test
npm run build
cd python && python -m build && pytest
cd php && composer install && vendor/bin/phpunitLicense
MIT. Unicode data: Unicode License V3 (see NOTICE).
