Risk Guard

What Risk Guard is

Risk Guard examines the third-party packages a project depends on and scores how risky they are to rely on. It checks your own source and your direct dependencies, and gathers everything into a single report — run it on your machine, or automatically on every pull request.

Ecosystems: npm, PyPI, RubyGems.

Open source on GitHub github.com/Risk-Guard/oss-risk-guard · MIT

How it works

  1. 1

    Inventory. Risk Guard reads your repository and builds an SBOM (SPDX or CycloneDX) of your project's packages. You can export it on its own, too.

  2. 2

    Score. Each check runs against your source and each direct dependency.

  3. 3

    Report. Results merge into one SARIF report — the standard format GitHub and GitLab render inline, so findings appear where you review code.

Optional — to enforce it on every change

  1. 4

    Define policy. One .risk-guard.yml in git sets which findings block, warn, or are acknowledged. A sensible default ships, so this is optional.

  2. 5

    Protect PRs. Run it in CI in active mode and risky dependencies fail the check before they merge.

Not a vulnerability scanner

Risk Guard doesn't scan for CVEs and doesn't replace the tool that does — run it alongside your vulnerability scanner, such as Snyk, Dependabot, Trivy, Grype, or OSV-Scanner, which catch known vulnerabilities in the versions you already ship. Risk Guard scores something they don't: supply-chain and provenance risk — who maintains a package, whether its source can be verified, and what it does at install time.

What it scores

Four areas of supply-chain risk:

Security
Missing security practices and unsafe configuration, such as a project with no security policy or a package that points to an insecure (http://) source.
License compliance
Packages with missing or undeclared licenses.
Maintenance & continuity
Abandoned or stale projects, single-maintainer packages, or source code that has drifted far ahead of its last published release.
Provenance
Whether a package is what it claims to be: mismatches between a package and its stated source, install-time scripts, and downloaded artifacts that fail hash verification.

Much of this risk exists before any CVE is filed — and sometimes a CVE is never filed at all. A package taken over by a new maintainer, or quietly abandoned, is a real risk that vulnerability databases won't describe.

Why it works this way

It's preventative

Risk Guard runs at the pull request and can fail the build before a risky dependency is merged. The decision happens at adoption time — you keep the dependency out, rather than discovering it after it's already in production. A dependency you never merge is one you never have to triage, patch, or respond to.

Because its checks score intrinsic risk signals — abandonment, provenance, install scripts, licensing — it can flag a problem that no vulnerability has been published for yet.

It doesn't overload your developers

Each finding belongs to a risk category, and the default policy gives each category a severity. Out of the box, only the critical category (for example, a tampered artifact) and continuity problems in your direct dependencies block the build — everything else is a warning. It's a short, readable file you can see and change, kept in git: owners set it once, developers don't each re-triage, and a finding you acknowledge stops blocking so it doesn't resurface on every pull request. Findings appear inline on the diff, at review time.

Reading the results

Each finding names a subject (a package, or your own repository), describes the issue, and references the rule that produced it and where it applies. A local run summarizes them:

Severity Subject Finding Rule
⚠️ warning nokogiri (rubygems) native extension runs at install time PACKAGE_INSTALL_SCRIPTS
⚠️ warning your repository a dependency's registry URL doesn't match its source repo PACKAGE_SOURCE_URL_MISMATCH
⬜ info is-odd@3.0.1 (npm) last repository commit was 2919 days ago SOURCE_REPO_ABANDONED

Every finding has a severityblocking, warning, or ignore — assigned by a default policy per risk category. Your .risk-guard.yml overrides it. The run's mode is the master switch:

Tuning it

Everything lives in one file at the repository root. Override a severity, or acknowledge a finding you've accepted — it stays in the report but no longer blocks:

# .risk-guard.yml
version: 2
workflow:
  mode: active

severity:
  check/PACKAGE_INSTALL_SCRIPTS: warning     # downgrade one check

expected_failures:                            # accept a finding; still reported
  package/npm/lodash:
    checks: [PACKAGE_STALE_RELEASE]
    reason: "Pinned deliberately; upgrade tracked in #482"
    expires: 2026-09-01T00:00:00Z

The configuration reference covers the full selector grammar, per-environment rules, performance, and private registries.

Privacy

Risk Guard reads public information from the package registries (npm, PyPI, RubyGems) and from projects' source repositories, so it needs network access to them. It runs entirely on your machine or CI runner — your code and your dependency list are never uploaded to any Risk Guard server.

Using it

Install

Linux and macOS — verifies a checksum before installing:

curl -fsSL https://risk-guard.github.io/oss-risk-guard/get.sh | sh

Then scan your repository — init runs a first scan and writes a starter .risk-guard.yml from the findings:

risk-guard init

In CI

The Risk Guard Action — findings appear in the Security tab and inline on pull requests:

- uses: actions/checkout@v6
- uses: Risk-Guard/action@v1