While researching typosquatting across package registries, I found two attack vectors not currently covered by typomania that have been used in documented malicious packages.
Proposed checks
1. Homoglyph
Replaces characters with visually similar alternatives:
l ↔ 1, i, I, |
o ↔ 0
e ↔ 3
a ↔ 4
s ↔ 5
rn ↔ m
vv ↔ w
Documented attacks:
2. Keyboard-adjacent
Replaces characters with adjacent keys on QWERTY:
s → a, z, w, d
e → w, r, d
o → i, p, k, l
- etc.
This is distinct from the existing typos check which targets common misspellings. Keyboard-adjacent systematically generates all single-character replacements based on physical key proximity.
Documented attacks:
All examples above are from the ecosyste-ms/typosquatting-dataset.
Variant generation
For a 10-character package name, keyboard-adjacent generates roughly 50-80 single-character variants (depends on which characters appear). This is comparable to the existing omitted check which generates n * alphabet_size candidates.
I ran both checks against critical packages on RubyGems and crates.io (~2000 packages). After filtering out packages with high download counts or that predate their targets, the results were manageable - dozens of candidates per registry, not thousands.
Implementation
Both checks are lookup tables. Reference implementations in Ruby:
Happy to submit PRs if there's interest.
While researching typosquatting across package registries, I found two attack vectors not currently covered by typomania that have been used in documented malicious packages.
Proposed checks
1. Homoglyph
Replaces characters with visually similar alternatives:
l↔1,i,I,|o↔0e↔3a↔4s↔5rn↔mvv↔wDocumented attacks:
1odashtargetinglodashon npm (spellbound-paper)r3queststargetingrequestson PyPI (datadog/malicious-software-packages-dataset)2. Keyboard-adjacent
Replaces characters with adjacent keys on QWERTY:
s→a,z,w,de→w,r,do→i,p,k,lThis is distinct from the existing typos check which targets common misspellings. Keyboard-adjacent systematically generates all single-character replacements based on physical key proximity.
Documented attacks:
requeztstargetingrequestson PyPI -s→z(lxyeternal/pypi_malregistry)requeatstargetingrequestson PyPI -s→a(lxyeternal/pypi_malregistry)All examples above are from the ecosyste-ms/typosquatting-dataset.
Variant generation
For a 10-character package name, keyboard-adjacent generates roughly 50-80 single-character variants (depends on which characters appear). This is comparable to the existing omitted check which generates
n * alphabet_sizecandidates.I ran both checks against critical packages on RubyGems and crates.io (~2000 packages). After filtering out packages with high download counts or that predate their targets, the results were manageable - dozens of candidates per registry, not thousands.
Implementation
Both checks are lookup tables. Reference implementations in Ruby:
Happy to submit PRs if there's interest.