Dependency review is GitHub's pull-request gate that diffs the dependencies a PR adds or changes against the GitHub Advisory Database and your license policy, and it blocks the merge before a vulnerable package ever lands on your default branch. It is the cheapest supply-chain control most teams have not turned on: shift the check from "scan main nightly" to "stop it at the PR," and remediation cost drops from a ticket to a review comment. Setup takes about twenty minutes. Here is the whole thing, including the sharp edges.
What it actually does
When a PR touches a manifest or lockfile that the dependency graph understands — package-lock.json, requirements.txt, go.mod, pom.xml, Cargo.lock, and the rest of the supported list — GitHub computes the dependency diff: every package added, removed, or version-changed. The dependency-review-action then evaluates that diff against three things: known advisories (GHSA), your license allow/deny lists, and optionally a list of packages you have banned outright. It sees only the diff. Pre-existing vulnerabilities in the base branch do not fail the check, which is exactly the property that makes it politically survivable — developers are gated on their change, not on five years of inherited debt.
Enable the prerequisites
Dependency graph is on by default for public repos. For private repos: Settings → Security → Code security and analysis → enable Dependency graph. The review action is free for public repositories; private repositories need GitHub Advanced Security (the Code Security SKU, after GitHub split GHAS licensing in 2025).
Organization-wide, do this once from the org's Code security settings rather than repo by repo — new repositories inherit it and you avoid the coverage gaps that turn up in every audit.
Add the workflow
Create .github/workflows/dependency-review.yml:
name: Dependency review
on: pull_request
permissions:
contents: read
pull-requests: write # needed for comment-summary-in-pr
jobs:
dependency-review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/dependency-review-action@v4
with:
fail-on-severity: high
comment-summary-in-pr: always
deny-licenses: GPL-3.0, AGPL-3.0
fail-on-scopes: runtime
Notes on the choices:
fail-on-severity: highblocks high and critical advisories. Starting atcriticalonly is defensible for a noisy monorepo; going down tomoderateis usually premature until the team trusts the gate.comment-summary-in-pr: alwaysposts the findings as a PR comment instead of burying them in the Actions log. This one option roughly doubles how often developers act on the result, in my experience rolling this out across a 200-repo org.fail-on-scopes: runtimeskips development-only dependencies. A vulnerabledevDependencyin a test helper is real but it is not "block the release" real.- License names must be valid SPDX identifiers, and you get either
allow-licensesordeny-licenses, not both.
Then make it mandatory: Settings → Branches (or a repository ruleset) → require the dependency-review check before merging. Without required-status enforcement you have built a suggestion, not a gate.
Gate licenses like you mean it
For OSPO purposes the license gate is arguably the bigger win, because license problems are far more expensive to remove after they ship. A deny-list of AGPL-3.0 and SSPL-1.0 for anything distributed is a common floor. If you maintain a formal policy, express it as allow-licenses instead — a short allowlist (MIT, Apache-2.0, BSD-2-Clause, BSD-3-Clause, ISC) fails closed on the weird stuff, including packages whose license GitHub cannot determine, which you want a human to look at anyway.
allow-dependencies-licenses exists for the inevitable exception: a specific purl you have taken legal advice on and accepted.
What dependency review misses
Three gaps to know about before you rely on it:
- It only triggers on manifest changes. An advisory published after your dependency merged will never fail this check — that is Dependabot's job, and the two are complements, not substitutes. (See our notes on Dependabot's update behavior for how that half works.)
- It only sees what the dependency graph sees. Dependencies resolved at build time — Dockerfiles, Bazel, vendored tarballs — are invisible unless you submit them yourself via the dependency submission API.
- No reachability, no exploit context. The action knows an advisory exists; it does not know whether the vulnerable function is called or whether an exploit is circulating. Teams that outgrow severity-only gating typically layer a reachability-aware SCA on the same PR event — Safeguard runs this as a second check that can downgrade unreachable findings instead of blocking on them, and the Snyk comparison covers how the major tools differ here.
Frequently asked questions
Does dependency review work on private repositories without paying?
No. Private repos need the GitHub Advanced Security Code Security license. Public repositories get the action free, which is why open source projects should turn it on today.
Why did my PR pass even though the repo has known vulnerabilities?
By design, the action evaluates only the dependency diff in the PR. Existing findings on the base branch belong to Dependabot alerts and your backlog process, not to the PR gate.
Can I make it warn without blocking while the team adjusts?
Yes — set warn-only: true in the action config, keep comment-summary-in-pr: always, and flip to enforcement after a sprint or two of clean signal. Just set a date, or warn-only becomes permanent.
Which ecosystems are covered?
Anything the dependency graph parses: npm, PyPI, Maven, NuGet, Go, RubyGems, Cargo, Composer, and GitHub Actions workflows themselves, among others. If your ecosystem is not parsed, use the dependency submission API to feed the graph from your build.