DepVet / Guides

Scan a lockfile for vulnerable dependencies in CI or agents

A lockfile records the exact versions you will run. Sending those pairs to a batch endpoint gives you a vulnerability and risk verdict per package without installing a scanner.

The problem

Audit tools need a package manager, network access to its advisory feed and often an account. Inside a sandboxed agent or a minimal CI image that is more setup than the check is worth, and the output is aimed at humans reading a terminal.

How /batch answers it

POST /batch accepts ecosystem and up to 20 {name, version} pairs and returns one light check per package (the same fields as /check), a summary count of OK, REVIEW and AVOID, and an overallVerdict. A package that cannot be found does not fail the call: it appears as an item with error: "not_found" and no verdict, and is counted under errors. For a lockfile, read the entries of packages in a lockfileVersion 2 or 3 package-lock.json, split them into chunks of 20, send each chunk and fail the build on any AVOID or on severities you choose. The GET form takes packages=name@version,name2.

Request and response

Example with three packages; the last one has been unpublished from npm.

$ curl -i "https://depvet.imac2014ville.workers.dev/batch?ecosystem=npm&packages=express,lodash@4.17.20,event-stream@3.3.6"
HTTP/2 402
payment-required: eyJ4NDAyVmVyc2lvbiI6Mi4uLn0=   # base64 JSON: scheme "exact", network eip155:8453,
                                                  # asset USDC, amount 50000 (= $0.05)
# An x402 client signs the payment, then retries with a PAYMENT-SIGNATURE header.

The paid response (abridged sample):

{
  "ok": true,
  "ecosystem": "npm",
  "count": 3,
  "overallVerdict": "REVIEW",
  "summary": { "OK": 1, "REVIEW": 1, "AVOID": 0, "errors": 1 },
  "results": [
    { "name": "express", "version": "5.3.0", "verdict": "OK", "reasons": [] },
    { "name": "lodash", "version": "4.17.20", "verdict": "REVIEW",
      "vulnerabilities": { "count": 5, "maxSeverity": "HIGH", "malware": false },
      "reasons": ["2 high-severity vulnerability advisory(ies) affect this version."] },
    { "name": "event-stream", "version": "3.3.6", "verdict": null, "error": "not_found" }
  ]
}

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 lock = JSON.parse(readFileSync("package-lock.json", "utf8"));
const pkgs = Object.entries(lock.packages).filter(([path]) => path)
  .map(([path, p]) => ({ name: path.split("node_modules/").pop(), version: p.version }));
for (let i = 0; i < pkgs.length; i += 20) {
  const res = await pay("https://depvet.imac2014ville.workers.dev/batch", { method: "POST", headers: { "content-type": "application/json" },
    body: JSON.stringify({ ecosystem: "npm", packages: pkgs.slice(i, i + 20) }) });
  const r = await res.json();
  if (r.summary?.AVOID) process.exitCode = 1;
}

Pricing

/batch costs $0.05 per call of up to 20 packages, about $0.0025 per package. A lockfile with 400 packages is 20 calls, or $1.00. 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