mlab docs

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 lockfileRequest sizeCooldown between scans
Anonymous (per IP)5124 MiB10 s
Free account, with a token10248 MiB5 s
Paid plan, with a token409632 MiBnone

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

InputDefaultDescription
path(auto-detect)Lockfile(s) or SBOM(s) to scan, newline- or comma-separated.
working-directory.Base directory for auto-detection.
formatautoForce the parser: npm, yarn, pnpm, cargo, pip, poetry, uv, pdm, pipfile, composer, gem, go, maven, nuget, pub, hex, mise, cyclonedx, spdx.
fail-onanyMinimum severity that fails the build: any, critical, high, medium, low, none.
fail-on-incompletefalseAlso fail when some package was not scanned (manifest over your package limit, git or path dependency, outage). Reported either way.
soft-failfalseNever fail the build: report findings and set outputs only.
token(none)vuln.mlab.sh API token. Strongly recommended for CI.
api-urlhttps://vuln.mlab.sh/api/v2/scanOverride 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

OutputDescription
totalVulnerabilities found across all lockfiles, one per package and CVE.
vulnerable-packagesNumber of distinct vulnerable packages.
critical, high, medium, low, unknownFindings per severity.
incompletetrue if some package was not scanned (limit, unscannable entry, outage, or a lockfile that failed).
not-scannedPackages left out because a manifest exceeded the package limit.
failedtrue 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-xxxx

Ids 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

On this page