Skip to content

Repository files navigation

pubguardian

pub version License

pubguardian scans your Dart and Flutter project's dependencies for security vulnerabilities, bad licenses, abandoned packages, and risky version constraints — all from a single command.

Run it locally before a release, or drop it into CI to block on findings.


What it catches

Check What triggers it
CVEs Your locked packages matched against the OSV.dev advisory database
Bad licenses GPL, AGPL, SSPL (and more, depending on your policy)
Abandoned packages No pub.dev release in over a year
Discontinued packages Publisher officially marked it discontinued
Risky constraints any, *, open-ended >=x.y.z, pre-1.0 carets

Quick start

Install globally and run it from your project root:

dart pub global activate pubguardian
pubguardian scan

That's it. It reads your pubspec.lock and pubspec.yaml automatically.

Make sure the global bin directory is in your PATH:

macOS / Linux (bash, zsh):

export PATH="$PATH:$HOME/.pub-cache/bin"

Windows (PowerShell):

$env:Path += ";$env:LOCALAPPDATA\Pub\Cache\bin"

Windows (cmd):

setx PATH "%PATH%;%LOCALAPPDATA%\Pub\Cache\bin"

What the output looks like

pubguardian  —  my_app 1.0.0
────────────────────────────────────────────────────────────────

CVE / Advisory Findings
────────────────────────────────────────────────────────────────

http 0.13.0 (direct)
  [CRITICAL] GHSA-4rgh-jx4f-qfcq · CVE-2024-12345
  Request smuggling via malformed headers
  Fix: upgrade to 0.13.6 or later
  https://osv.dev/vulnerability/GHSA-4rgh-jx4f-qfcq

License Compliance Findings
────────────────────────────────────────────────────────────────

some_gpl_package 2.0.0 (transitive)
  [STRONG-COPYLEFT] GPL-3.0-only
  Requires derivative works to also be licensed as GPL-3.0-only.

Package Health Findings
────────────────────────────────────────────────────────────────

  ✖ flutter_legacy 0.3.0
    Package is officially discontinued. Replacement: flutter_new.

  ⚠ old_helper 1.2.0
    No release in 847 days (2.3 years). Latest on pub.dev: 1.2.0.

Constraint Warnings
────────────────────────────────────────────────────────────────

  [HIGH]    shared_prefs: "any"
    Accepts any version. Pin to a range like "^2.0.0".

────────────────────────────────────────────────────────────────
Summary
────────────────────────────────────────────────────────────────
  Packages scanned        38
  CVE / advisories        1
  License violations      1
  Health issues           2
  Constraint warnings     1

Exit code is 1 when any CVE finding, license error, HIGH health issue, or HIGH constraint warning is present — so it blocks CI automatically.


Common commands

# Scan and show everything
pubguardian scan

# Only care about HIGH and CRITICAL CVEs (good for blocking CI)
pubguardian scan --min-severity high

# Skip the pub.dev network calls — faster, works offline
pubguardian scan --skip-health

# Strict mode: flag LGPL and MPL-2.0 as well
pubguardian scan --license-policy strict

# Suppress a known false-positive
pubguardian scan --ignore GHSA-xxxx-yyyy-zzzz

# Output SARIF for GitHub Advanced Security
pubguardian scan --format sarif --output results.sarif

# Generate a CycloneDX SBOM
pubguardian scan --format cyclonedx --output sbom.cdx.json

# Output structured JSON for scripting
pubguardian scan --format json

All options

pubguardian scan [options]

-l, --lockfile          pubspec.lock path          (default: pubspec.lock)
-p, --pubspec           pubspec.yaml path           (default: pubspec.yaml)
-f, --format            text | json | sarif | cyclonedx   (default: text)
-o, --output            Write output to a file instead of stdout
    --min-severity      critical | high | medium | low | unknown
    --license-policy    commercial | strict | permissiveOnly
    --abandoned-days    Days without a release before flagging  (default: 365)
-i, --ignore            Suppress a CVE/GHSA ID. Can be repeated.
    --skip-health       Skip pub.dev health checks
    --skip-license      Skip license analysis
    --skip-constraints  Skip constraint analysis
    --exit-zero         Always exit 0, even when findings exist

License policies explained

You pick how strict the license check should be:

Policy What gets flagged
commercial (default) GPL-2/3, AGPL-3, SSPL — copyleft that affects commercial products
strict Everything above plus LGPL, MPL-2.0, EUPL-1.2
permissiveOnly Anything that isn't MIT, Apache-2.0, BSD, ISC, or Unlicense
# Use strict mode if you're shipping a closed-source product
pubguardian scan --license-policy strict

Adding it to CI

GitHub Actions — block the build on HIGH+ CVEs

steps:
  - uses: actions/checkout@v5
  - uses: dart-lang/setup-dart@v1

  - name: Install pubguardian
    run: dart pub global activate pubguardian

  - name: Block on HIGH/CRITICAL CVEs
    run: pubguardian scan --min-severity high

GitHub Actions — full setup with SARIF + SBOM

steps:
  - uses: actions/checkout@v5
  - uses: dart-lang/setup-dart@v1

  - name: Install pubguardian
    run: dart pub global activate pubguardian

  # Scan and upload findings to GitHub's Security tab
  - name: Scan (SARIF)
    run: pubguardian scan --format sarif --output results.sarif --exit-zero

  - name: Upload to GitHub Advanced Security
    uses: github/codeql-action/upload-sarif@v4
    with:
      sarif_file: results.sarif

  # Generate a software bill of materials and keep it as an artifact
  - name: Generate SBOM
    run: pubguardian scan --format cyclonedx --output sbom.cdx.json --exit-zero

  - uses: actions/upload-artifact@v5
    with:
      name: sbom
      path: sbom.cdx.json

  # Hard gate — fails the build if any HIGH or CRITICAL CVE is found
  - name: CVE gate
    run: pubguardian scan --min-severity high --skip-health --skip-license --skip-constraints

GitLab CI

security-scan:
  script:
    - dart pub global activate pubguardian
    - pubguardian scan --format sarif --output gl-sast-report.sarif --exit-zero
  artifacts:
    reports:
      sast: gl-sast-report.sarif

GitHub Action

Skip the manual install — scan every push with a 3-line workflow:

steps:
  - uses: sonofnos/pubguardian@v1
    with:
      args: scan --format sarif --output results.sarif --exit-zero

Make it a hard gate that fails the build on HIGH/CRITICAL CVEs:

steps:
  - uses: sonofnos/pubguardian@v1
    with:
      args: scan --min-severity high --skip-health
Input Default Description
args scan --exit-zero Arguments passed to the CLI. Omit --exit-zero to fail on findings.
working-directory . Directory containing pubspec.yaml / pubspec.lock.
version latest Pin a specific version, e.g. 0.1.8.

pre-commit

Block commits that introduce vulnerable dependencies (requires dart pub global activate pubguardian once):

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/sonofnos/pubguardian
    rev: v0.1.8
    hooks:
      - id: pubguardian

Docker

docker build -t pubguardian .
docker run --rm -v "$PWD":/app -w /app pubguardian scan

Makefile

Common targets when developing the tool itself:

make install     # dart pub global activate --source path .
make scan        # text scan
make scan-sarif  # results.sarif
make sbom        # sbom.cdx.json
make build       # native binary to build/pubguardian
make docker      # build the docker image

Using it as a library

pubguardian exposes its full pipeline if you want to integrate scanning into your own tooling:

import 'package:pubguardian/pubguardian.dart';

void main() async {
  final runner = Runner();

  final result = await runner.run(
    const ScanConfig(
      minSeverity: Severity.high,
      licensePolicy: LicensePolicy.commercial,
    ),
    onStatus: print,
  );

  runner.close();

  final report = result.report;
  print('${report.totalVulnerabilities} CVEs across ${report.totalPackages} packages');

  for (final pkg in report.vulnerablePackages) {
    for (final v in pkg.vulnerabilities) {
      print('${pkg.name}: ${v.id} (${v.severity.name})');
    }
  }
}

How it works under the hood

  1. Reads pubspec.lock — gets the exact resolved version of every hosted dependency.
  2. Sends them to api.osv.dev/v1/querybatch in batches of 100. Retries on transient errors.
  3. Hits pub.dev/api/packages/{name} to check if each package is discontinued or stale.
  4. Calls api.deps.dev to get the SPDX license identifier for each package (pub.dev doesn't expose this in its API).
  5. Runs the licence, health, and constraint checks in-process.
  6. Prints or writes the result in whichever format you asked for.

License

Apache 2.0 — see LICENSE.

About

Supply-chain security scanner for Dart and Flutter projects

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages