mlab docs

SBOM scan (API)

Scan a whole lockfile or SBOM for known vulnerabilities - by URL or by POSTing it - no browser required.

The /scan page parses your lockfile in the browser and checks every dependency against OSV. GET/POST /api/v2/scan is the same scanner, reachable from a script or CI: hand it a lockfile by URL or as the request body, and it parses server-side and returns one result per dependency.

Unlike /api/v2/query (one coordinate at a time), this scans the whole manifest in a single request.

Input modes

HowExample
URL (GET)GET /api/v2/scan?url=https://…/Cargo.lock
URL (POST)body {"url":"https://…/Cargo.lock"}
Raw body--data-binary @Cargo.lock
Inline textbody {"manifest":"…","format":"cargo"}
Pre-parsedbody {"packages":[{ecosystem,name,version}|{purl}]}

format (query or body) forces the parser; the default auto sniffs from the URL/filename then the content. Detected: npm (package-lock.json), cargo (Cargo.lock), pip (requirements.txt, == pins), composer (composer.lock), gem (Gemfile.lock), go (go.sum), CycloneDX SBOM (JSON, via purl), and mise (mise.lock).

Scan by URL

curl -s "https://vuln.mlab.sh/api/v2/scan?url=https://raw.githubusercontent.com/rust-lang/cargo/master/Cargo.lock"

Scan a local lockfile

POST the file straight from disk - the format is auto-detected:

curl -s -X POST "https://vuln.mlab.sh/api/v2/scan" --data-binary @Cargo.lock

Force the format for an ambiguously-named file:

curl -s -X POST "https://vuln.mlab.sh/api/v2/scan?format=npm" --data-binary @my-lock.json

Response

{
  "hash": "8a1564f1d99d2afa",
  "cached": false,
  "count": 2,
  "truncated": false,
  "ttl": 21600,
  "packages": [ { "ecosystem": "crates.io", "name": "time", "version": "0.1.44" } ],
  "results": [ { "ok": true, "vulns": [ /* OSV vuln objects */ ] } ]
}
  • results[i] lines up with the scanned coordinate i. For the URL / raw / inline modes a packages array echoes those coordinates in the same order, so the result is self-describing.
  • results[i].ok == false means that coordinate could not be scanned (upstream outage, or an unqueryable coordinate). It is not a clean result - never treat it as "no vulns".
  • truncated: true means the manifest exceeded the 512-package ceiling and only the first 512 were scanned.

No auth is required, but for CI use an API token - generate one at /me/tokens and pass it as Authorization: Bearer <token> (or X-API-Key). A token gets 25 scans / hour; anonymous callers share only 8 / hour per IP, which is unreliable behind shared CI runner egress IPs. A signed-in browser session is unlimited.

Use in CI/CD

The official GitHub Action wraps all of this - auto-detects lockfiles, applies a severity threshold, writes a job summary. Store your token as a repo secret (VULN_MLAB_TOKEN):

- uses: mlab-sh/vuln-scan-action@v1
  with:
    fail-on: high
    token: ${{ secrets.VULN_MLAB_TOKEN }}

Prefer to scan from your editor? The VS Code extension runs the same scan on demand on any lockfile, with results shown inline.

Or roll your own gate with curl - fail the build when any dependency has a known vulnerability:

curl -s -X POST "https://vuln.mlab.sh/api/v2/scan" \
  -H "Authorization: Bearer $VULN_MLAB_TOKEN" --data-binary @Cargo.lock \
  | jq -e '[.results[].vulns // [] | length] | add == 0' > /dev/null \
  || { echo "Vulnerable dependencies found"; exit 1; }

The URL modes fetch a URL you supply, so an SSRF guard applies: http/https only, ports 80/443 only, and the target must resolve to a public address (private, loopback, link-local and cloud-metadata ranges are refused). Redirects are followed (≤ 4) with the same check, and the body is capped at 8 MiB.

See also

On this page