DepVet / Guides

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

More guides

Sister services