GitHub Action
Scan your lockfiles and SBOMs for known CVEs on every push or pull request with the official vuln.mlab.sh GitHub Action.
The official mlab-sh/vuln-scan-action scans your lockfiles and SBOMs for known CVEs on every push or pull request, powered by vuln.mlab.sh. It auto-detects lockfiles, checks every dependency against OSV and NVD, annotates the vulnerable lines in the pull request, writes a job summary, and fails the build (or just reports) on a severity threshold.
No dependency install, no local database. Parsing and scanning happen server-side: the Action uploads your lockfile and reports the result. It runs on node24.
Quick start
name: SBOM scan
on: [push, pull_request]
jobs:
sbom:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: mlab-sh/vuln-scan-action@v1
with:
fail-on: high
token: ${{ secrets.VULN_MLAB_TOKEN }}With no inputs, it auto-detects the supported lockfiles in the repository and fails on any known vulnerability.
Why a token
| Packages per lockfile | Request size | Cooldown between scans | |
|---|---|---|---|
| Anonymous (per IP) | 512 | 4 MiB | 10 s |
| Free account, with a token | 1024 | 8 MiB | 5 s |
| Paid plan, with a token | 4096 | 32 MiB | none |
GitHub-hosted runners share egress IPs across many repositories, so an anonymous cooldown can be taken by someone else's job. The Action waits out the cooldown the server announces and retries, but a token is faster and steadier, and its scans land in your history on vuln.mlab.sh.
Sign in at vuln.mlab.sh/me/tokens and generate a token.
Add it as a repository secret named VULN_MLAB_TOKEN.
Pass it with the token: input, as above.
Inputs
| Input | Default | Description |
|---|---|---|
path | (auto-detect) | Lockfile(s) or SBOM(s) to scan, newline- or comma-separated. |
working-directory | . | Base directory for auto-detection. |
format | auto | Force the parser: npm, yarn, pnpm, cargo, pip, poetry, uv, pdm, pipfile, composer, gem, go, maven, nuget, pub, hex, mise, cyclonedx, spdx. |
fail-on | any | Minimum severity that fails the build: any, critical, high, medium, low, none. |
fail-on-incomplete | false | Also fail when some package was not scanned (manifest over your package limit, git or path dependency, outage). Reported either way. |
soft-fail | false | Never fail the build: report findings and set outputs only. |
token | (none) | vuln.mlab.sh API token. Strongly recommended for CI. |
api-url | https://vuln.mlab.sh/api/v2/scan | Override the scan endpoint. |
Auto-detected: Cargo.lock, package-lock.json, npm-shrinkwrap.json, yarn.lock, pnpm-lock.yaml, composer.lock, Gemfile.lock, go.sum, requirements.txt, poetry.lock, uv.lock, pdm.lock, Pipfile.lock, pom.xml, gradle.lockfile, packages.lock.json, pubspec.lock, mix.lock, mise.lock, and CycloneDX SBOMs named bom.json or bom.xml (node_modules, vendor, target and similar are skipped). Any other SBOM, SPDX included, can be passed in path.
Outputs
| Output | Description |
|---|---|
total | Vulnerabilities found across all lockfiles, one per package and CVE. |
vulnerable-packages | Number of distinct vulnerable packages. |
critical, high, medium, low, unknown | Findings per severity. |
incomplete | true if some package was not scanned (limit, unscannable entry, outage, or a lockfile that failed). |
not-scanned | Packages left out because a manifest exceeded the package limit. |
failed | true if the gate failed (threshold met, a lockfile could not be scanned, or incomplete with fail-on-incomplete), regardless of soft-fail. |
.mlab/vuln.yml
The Action reads the same per-repository file as the VS Code extension, from the repository root:
# Skip the scan entirely in this repo.
# disable: true
# Advisories accepted as known risk: not reported, never fail the build.
ignoreVulns:
- CVE-2024-0000 # not reachable: only used in tests
- GHSA-xxxx-xxxx-xxxxIds are matched against the advisory id and all its aliases.
Examples
Report only, gate on the outputs yourself:
- id: scan
uses: mlab-sh/vuln-scan-action@v1
with:
soft-fail: "true"
token: ${{ secrets.VULN_MLAB_TOKEN }}
- run: echo "Found ${{ steps.scan.outputs.critical }} critical vulnerabilities"Fail on critical only, and on any gap in coverage:
- uses: mlab-sh/vuln-scan-action@v1
with:
path: server/Cargo.lock
fail-on: critical
fail-on-incomplete: "true"
token: ${{ secrets.VULN_MLAB_TOKEN }}Monorepo, several lockfiles:
- uses: mlab-sh/vuln-scan-action@v1
with:
path: |
frontend/package-lock.json
backend/go.sum
infra/requirements.txt
token: ${{ secrets.VULN_MLAB_TOKEN }}What it reports
- One finding per package and CVE: advisories that describe the same CVE (GHSA, RUSTSEC, PYSEC) are merged, keeping the worst severity. A CVSS vector is scored when an advisory has no severity label.
- Annotations on the lockfile line that declares each vulnerable package, so they show in the pull request diff, with the fixed version and the advisory summary.
- A job summary: coverage per lockfile, a "Not fully scanned" section with the server's explanation, the findings with the version that fixes them, your tier's limits and what the next tier unlocks.
- Packages the scan did not cover are never counted as clean. A lockfile that cannot be scanned at all (over the size limit, unreachable server) fails the gate after the other lockfiles are scanned and reported.
See also
- SBOM scan: the API the Action wraps.
- Integrations for the scan: the same scan in VS Code, JetBrains, the CLI, pre-commit and AI assistants.
n8n Integration
Automate mlab.sh scanning, CVE intelligence and threat-actor data from your n8n workflows with the official community node package.
VS Code Extension
Scan your lockfiles and SBOMs for known CVEs from inside VS Code, with exploitation intelligence, diagnostics and CVE hovers, powered by vuln.mlab.sh.