Too many Dependabot alerts? Triage by what actually runs

Short answer: don't read alerts one by one. First cut the noise with GitHub's own free controls (auto-triage rules, grouped security updates), then collapse what is left to one line per package, then rank by whether it is exploited, reachable and actually deployed. Dependabot is good at the first two steps; the last, "is this what runs in production?", needs a different signal.

GitHub itself puts it this way: “Dependabot alerts can add up faster than a team can work through them” (changelog, 2026-10-28). We are not trying to replace Dependabot. It is free, it opens the fix pull requests, and the best advice is to use it well. This page is a method, a small script we tested, and a note on where a second signal helps.

Step 1: use what GitHub already gives you

Facts below are from GitHub’s documentation, read 2026-11-01.

  • Auto-triage rules run before notifications are sent, so rules that auto-dismiss low-risk alerts cut notification noise. There are two kinds: GitHub presets (available for all repositories) and custom rules (public repositories, and organization repositories with GitHub Code Security) (docs).
  • A preset dismisses low-impact issues in development-scoped npm dependencies: vulnerabilities that are “unlikely to be exploitable in a developer (non-production or runtime) environment.” It is on by default for public repositories and opt-in for private ones.
  • A caution from the same page: the preset “Dismiss package malware alerts” auto-dismisses alerts that flag all versions of a package as malicious, to avoid false positives for internal packages whose name collides with a malicious public one. GitHub warns that if a contributor adds a dependency that is truly malicious in all versions, that rule will dismiss the alert. Think before enabling it.
  • Grouped security updates put many updates in one pull request, either across directories per ecosystem or by rules in dependabot.yml such as package name, dev or production dependencies, and SemVer level (docs).
  • A digest instead of a stream: a weekly email digest is available if you don’t want every notification (alerts docs).

Step 2: collapse alerts to one line per package

Ten advisories against one package are one task, not ten: upgrade the package. We wrote a small script that takes dynv scan --json output (see CI gating) and groups it. We ran it on a lockfile with [email protected], [email protected], [email protected] and, as a dev-only transitive, [email protected] (a dependency of [email protected]):

// triage.mjs
import { readFileSync } from 'node:fs';
const rank = { critical: 4, high: 3, medium: 2, low: 1, unknown: 0 };
const num = (v) => String(v).split(/[^0-9]+/).filter(Boolean).map(Number);
const cmp = (a, b) => { const x = num(a), y = num(b); for (let i = 0; i < Math.max(x.length, y.length); i++) if ((x[i] ?? 0) !== (y[i] ?? 0)) return (x[i] ?? 0) - (y[i] ?? 0); return 0; };
const report = JSON.parse(readFileSync(0, 'utf8'));
const by = new Map();
for (const f of report.findings) {
  const k = `${f.package}@${f.version}`;
  const g = by.get(k) ?? { k, direct: f.direct, worst: 'unknown', n: 0, fix: null, noFix: false };
  g.n++;
  if (rank[f.severity] > rank[g.worst]) g.worst = f.severity;
  if (f.fixed) g.fix = !g.fix || cmp(f.fixed, g.fix) > 0 ? f.fixed : g.fix; else g.noFix = true;
  by.set(k, g);
}
const rows = [...by.values()].sort((a, b) => rank[b.worst] - rank[a.worst] || b.n - a.n);
console.log('package             severity  advisories  direct  one upgrade clears them');
for (const g of rows) console.log(`${g.k.padEnd(19)} ${g.worst.padEnd(9)} ${String(g.n).padEnd(11)} ${String(g.direct).padEnd(7)} ${g.noFix ? 'some have no fix' : g.fix}`);
$ dynv scan --fail-on none --json | node triage.mjs
package             severity  advisories  direct  one upgrade clears them
[email protected]         critical  3           true    16.4.1
[email protected]      critical  2           true    1.2.6
[email protected]      high      6           true    4.18.0
[email protected] high      4           false   7.1.3

Fifteen findings became four lines, each with the version that clears everything listed for that package. Treat “one upgrade clears them” as a hint, not a plan: it takes the highest fixed version in the list, which can be a major upgrade (here serialize-javascript 7.1.3 is far from 2.1.0, and the real fix is to update mocha, the direct dependency that brings it in). Read the changelog before bumping. The scan output does not say whether a dependency is dev-only, so check that against your package.json.

Step 3: rank what is left

Ask these in order and stop when you have your answer for each package. They are the same questions as in our critical-CVE playbook:

  1. Is it exploited? CISA’s KEV catalog, the advisory text, and EPSS (a model that estimates the probability a CVE is exploited in the next 30 days, from FIRST). GitHub says to use reachability “alongside severity, EPSS scores, dependency scope, and dependency relationship,” which is a fair summary of the whole exercise.
  2. Is it a production dependency? Dev-only tooling that never ships is lower risk than anything on the request path (but a compromised dev dependency can still attack your build; see supply chain).
  3. Can your code reach it? GitHub added reachability analysis to Dependabot on 2026-10-28. It builds a CodeQL call graph and labels each alert Reachable, Potentially reachable, Unreachable or Reachability unavailable. As of the changelog it is a public preview, free during the preview, available on GitHub Enterprise Cloud for JavaScript and TypeScript repositories with an npm, Yarn or pnpm lockfile, and off by default. GitHub says verdicts “never close or dismiss an alert,” and its docs say not to dismiss an alert solely because it is Unreachable (changelog, docs). If you are outside that scope, read the advisory’s stated conditions yourself.
  4. Is the fix available and safe? Group the update, run tests, ship.

Step 4: add “is it deployed?”

Dependabot analyses your repository. It cannot tell you what is installed in the container that has been running since March, or in the service nobody remembered to redeploy. A repository alert for a version you already upgraded is noise; a missing alert for a version still running is the real risk.

Two ways to add that signal with Dynv, neither a replacement for Dependabot:

  • In CI: dynv scan gives you a deterministic gate on what the lockfile or installed tree contains right now (how).
  • In production: with monitoring on, each running service reports what it actually has installed, re-checked at boot, on change and every six hours, and you get count-only alerts (how). The “is it deployed?” question then has an answer per service rather than per repository.

Limits

  • Our script is a triage aid, not a scanner; its upgrade hint can mislead (see above).
  • Reachability is a static estimate and covers a narrow slice of ecosystems today. “Unreachable” lowers priority; it does not mean safe.
  • Dynv’s findings come from OSV.dev and Dependabot’s from the GitHub Advisory Database. The two overlap heavily but are not identical, so counts can differ.
  • KEV and EPSS are not shown by dynv scan yet; look them up for the CVEs that matter.

Facts last verified . Spotted an error? Tell us.

New here? Start here for the guides in a sensible order.