Building a typosquat detector: how far can you get with heuristics alone
Project: NPM Package Verifier (try the live demo)
Typosquatting on package registries is a solved problem in the sense that everyone agrees what it looks like: reqeust instead of request, crossenv instead of cross-env, a package name one keystroke away from something with ten million weekly downloads. It's a much less solved problem in the sense that "one keystroke away" is doing a lot of work, and turning that intuition into a scoring function that doesn't drown in false positives is genuinely fiddly.
What the heuristics actually check
The detector runs four name-similarity techniques against a database of known high-impact packages: Levenshtein edit distance (catches single-character typos like lodahs), homograph substitution (visually similar sequences: rn for m, vv for w, digits standing in for letters), hyphen/underscore swaps (cross_env vs cross-env), and combosquatting (a legitimate name padded with an extra word, like a fake babel-core-utils). Each technique that fires adds points, weighted by how deliberate it looks: a homograph swap is harder to type by accident than a single dropped letter, so it scores higher.
Name similarity alone isn't enough, though, because plenty of legitimate packages are one edit away from something bigger by pure coincidence, and because a real typosquat published five minutes ago won't have accumulated any other red flags yet. So the score also pulls in download counts and update recency, compared against whichever known package the name resembles: a package that looks like a typosquat of something with millions of weekly downloads, but itself has a few hundred, is a much stronger signal than the name match by itself. Downloads that are already large make the whole comparison moot: nobody is typosquatting a package that's already this popular in its own right, so above a threshold the download-gap check is skipped entirely rather than penalizing a large package for existing.
The third check has nothing to do with the name at all: it pulls the package's actual preinstall/install/postinstall scripts and runs them through a small regex rule set: remote shell execution (curl … | sh), base64 decoding, dynamic eval, environment-variable access, system fingerprinting commands, PowerShell invocation. This is the check that catches something a typosquat search would never surface: a legitimately-named, non-typosquatted package that's been compromised via a maintainer account takeover, where the name similarity heuristics have nothing to say.
Where heuristics alone stop working
Two honest limits. First, the comparison database is finite by necessity: the full project keeps roughly 5,700 high-impact packages and their generated typosquats offline in SQLite; the browser demo on this site narrows that to about 140 names it can hold client-side. Anything typosquatting a package outside that list is invisible to the name-similarity check, full stop. There's no way around this without either shipping a much larger dataset or querying a live index of the entire registry, which the demo deliberately doesn't do.
Second: every one of these checks is a proxy, not a certainty. A brand-new, low-download, recently-updated package with a name close to something popular gets flagged the same way whether it's malicious or just a legitimate new project that happens to share vocabulary. The install-script regex rules catch known-bad patterns, not obfuscated variants of them: base64 -d is easy to flag; the same behavior split across three chained string operations isn't. None of that makes the heuristics useless: the scoring bands are explicitly calibrated to be a triage signal ("worth a second look") rather than a verdict, and the tool always ends with a prompt asking the user to decide, not an automatic block.