Detect typosquatting in package.json dependencies
A single swapped letter, such as expresss for express, can pull in someone else's code. You can screen every dependency name in a manifest with one request.
The problem
Typosquatting depends on a typo or an autocomplete slip, and on nobody checking. Code review rarely catches it because the line looks right at a glance, and the package often even works by re-exporting the real library. The signal is the name itself, plus how new and thin the package is.
How DepVet flags it
DepVet compares each name against a built-in list of popular npm and PyPI packages and reports typosquatSuspect with the name it resembles when it is within one or two edits (very short names are skipped to avoid noise). A suspect name yields REVIEW. In the full /report, the verdict rises to AVOID when the package is also new or has three or fewer releases. To screen a manifest, read the keys of dependencies and devDependencies and send them to /batch, which takes up to 20 packages per call and returns a verdict per package plus an overallVerdict. Names without a version are checked at their latest release, which is enough for a name check.
Request and response
Example: a single misspelled name.
$ curl -i "https://depvet.imac2014ville.workers.dev/check?ecosystem=npm&name=expresss"
HTTP/2 402
payment-required: eyJ4NDAyVmVyc2lvbiI6Mi4uLn0= # base64 JSON: scheme "exact", network eip155:8453,
# asset USDC, amount 5000 (= $0.005)
# An x402 client signs the payment, then retries with a PAYMENT-SIGNATURE header.
The paid response (sample):
{
"ok": true,
"ecosystem": "npm",
"name": "expresss",
"version": "0.0.0",
"license": "ISC",
"vulnerabilities": { "count": 0, "maxSeverity": "NONE", "malware": false },
"typosquatSuspect": "express",
"verdict": "REVIEW",
"reasons": ["Name is 1 edit(s) from popular package \"express\"."]
}
JavaScript with @x402/fetch
import { wrapFetchWithPaymentFromConfig } from "@x402/fetch";
import { ExactEvmScheme } from "@x402/evm";
import { privateKeyToAccount } from "viem/accounts";
const signer = privateKeyToAccount(process.env.PRIVATE_KEY); // wallet holding USDC on Base
const pay = wrapFetchWithPaymentFromConfig(fetch, {
schemes: [{ network: "eip155:8453", client: new ExactEvmScheme(signer) }],
});
import { readFileSync } from "node:fs";
const pkg = JSON.parse(readFileSync("package.json", "utf8"));
const names = Object.keys({ ...pkg.dependencies, ...pkg.devDependencies }).slice(0, 20);
const res = await pay("https://depvet.imac2014ville.workers.dev/batch", {
method: "POST", headers: { "content-type": "application/json" },
body: JSON.stringify({ ecosystem: "npm", packages: names.map(name => ({ name })) }),
});
const { results } = await res.json();
console.log(results.filter(r => r.typosquatSuspect).map(r => r.name + " ~ " + r.typosquatSuspect));
Pricing
/check costs $0.005 per package and /batch costs $0.05 for up to 20 packages. There is no API key and no account: each request is paid in USDC on Base over x402. Malformed input returns 400 and failed lookups return a non-2xx status, so those are not charged.
Limitations
- The popular-name list is short (on the order of a hundred names per ecosystem), so a squat of a less famous package will not be recognised.
- Scoped names and packages that legitimately resemble each other can be flagged. REVIEW means look, not block.
- Manifest parsing is up to you; DepVet receives names, not files.
More guides
- Check an npm package for malware before you install it
- Scan a lockfile for vulnerable dependencies in CI or agents
DepVet overview/openapi.jsonllms.txt
Sister services
- BaseLens: Base chain tools: tx explainer, wallet snapshot, x402 endpoint check, web-to-markdown
- TokenGuard: honeypot and rug pull checks for Base tokens, plus pre-screened new launches
- MacroLens: country macro statistics and company registries (Norway, France)
- SkyFeed: weather forecasts, US alerts, earthquakes and public holidays
- ChainRead: gas, balances, ENS and Basename resolution on Base and Ethereum