← Back to Blog

GitHub Said 55 Vulnerabilities. Here Is How Dependabot Works, How to Set It Up, and How to Tell Which Alerts Matter

· 11 min read A plain amber robot arm sorting a pile of small parcels into two trays on a dark workbench, one tray glowing, one dim.

On October 30, 2026 we pushed a one-line fix to a private repository, and GitHub answered with this:

GitHub found 55 vulnerabilities on [our repository's] default branch (18 high, 23 moderate, 14 low).

That message comes from Dependabot, GitHub's built-in dependency scanner. Fifty-five sounds alarming. After an hour of checking, the honest answer was: every alert was technically correct, almost none was a real threat to how we use that code, and one tool we never use accounted for 22 of them.

This post explains how Dependabot works, how to turn it on and configure it, how to keep it from burying you in pull requests, and the triage method we used to sort the 55 alerts.

What Dependabot is

Dependabot is three related features, plus a few newer extras. The three core features cost nothing on GitHub.com, for public and private repositories alike.

Scroll to compare columns and rows when the table extends beyond the box.

Feature What it does Needs dependabot.yml?
Dependabot alerts Flags dependencies with a known vulnerability No
Dependabot security updates Opens a pull request that moves a vulnerable dependency to a patched version No (the file can customize it)
Dependabot version updates Opens pull requests to keep dependencies current, vulnerable or not Yes
Auto-triage rules Dismisses, reopens or routes alerts automatically No
Malware alerts Flags packages listed as malicious No

The first two are about security. The third is about staying current, and it is the one that floods a repository with pull requests if you leave it unconfigured.

How alerts are made

GitHub's documentation describes a simple pipeline:

  1. The dependency graph. GitHub reads the manifest and lock files committed to your repository's default branch (package.json and package-lock.json, requirements.txt, go.mod, Cargo.lock and so on) and builds a list of every package and version.
  2. The GitHub Advisory Database. Each advisory names a package and a vulnerable version range. "Only advisories reviewed by GitHub trigger alerts."
  3. The match. When a version in your graph falls inside a vulnerable range, you get an alert. Matching runs again whenever a new advisory is published or a push changes your dependencies.

Four consequences follow, and the first one surprised us:

  • Dependabot sees what is committed, not what is deployed. If your build generates or updates a lockfile, the alerts describe the old file in Git, not the software that runs.
  • It does not check whether the vulnerable code is ever called. A flaw in a library's WebSocket client raises the same alert whether or not you open a WebSocket. GitHub has a separate reachability analysis for this, in public preview for npm, pnpm, Yarn and TypeScript projects.
  • GitHub Actions pinned to a commit SHA get no alerts. Alerts cover actions referenced by a version tag. Version updates can still bump SHA pins.
  • Turning it on does not email you about old problems. GitHub notifies you only about vulnerabilities found after you enable alerts. The existing ones are on the Security tab.

Turn it on

In the repository, open Settings, then the security settings page (Advanced Security on current GitHub; older guides call it "Code security and analysis"). Enable, in this order:

  1. Dependency graph (on by default for public repositories).
  2. Dependabot alerts.
  3. Dependabot security updates, if you want fix pull requests opened for you.
  4. Grouped security updates, so a fix for one package arrives as one pull request instead of several.
  5. Dependabot version updates. This opens a template .github/dependabot.yml for you to fill in and commit.

Organization owners can apply the same settings to every repository at once from the organization's settings.

Configure version updates with dependabot.yml

Version updates do nothing until .github/dependabot.yml tells Dependabot which package managers to watch, where, and how often. This is a reasonable starting file for a website repository with npm packages, GitHub Actions workflows and Docker images:

version: 2
updates:
  - package-ecosystem: "npm"
    directory: "/"
    schedule:
      interval: "weekly"
      day: "monday"
      time: "06:00"
      timezone: "UTC"
    open-pull-requests-limit: 5
    cooldown:
      default-days: 5
      semver-major-days: 14
    groups:
      npm-minor-and-patch:
        update-types: ["minor", "patch"]
      npm-security:
        applies-to: security-updates
        patterns: ["*"]
    ignore:
      - dependency-name: "sharp"
        update-types: ["version-update:semver-major"]
    labels: ["dependencies"]
    commit-message:
      prefix: "deps"
      prefix-development: "deps-dev"
      include: "scope"

  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "monthly"
    groups:
      actions:
        patterns: ["*"]

  - package-ecosystem: "docker"
    directories: ["/services/*"]
    schedule:
      interval: "weekly"

What each part does, from the options reference:

Scroll to compare columns and rows when the table extends beyond the box.

Key What it controls
version: 2 Required; always 2
package-ecosystem The package manager: npm (also pnpm and Yarn), pip, uv, bundler, cargo, gomod, maven, gradle, nuget, composer, docker, docker-compose, github-actions, terraform, helm, pre-commit and more
directory / directories Where the manifest lives. directories takes a list and supports * globs
schedule interval is daily, weekly, monthly, quarterly, semiannually, yearly or cron (with a cronjob line). day, time and timezone refine it; time is UTC unless you set timezone
open-pull-requests-limit Most open version-update pull requests at once. Default 5; 0 turns version updates off. Security updates have their own limit
groups Combine updates into fewer pull requests by patterns, exclude-patterns, dependency-type, update-types. applies-to: security-updates makes a group for security fixes
cooldown Wait a number of days after a release before proposing it. Separate values for major, minor and patch. Security updates ignore it
ignore / allow Skip or limit dependencies by name, version or update type. ignore wins
labels, assignees, milestone Pull request housekeeping. labels: [] removes the default labels
commit-message Commit and pull request title prefix, separately for development dependencies
target-branch Branch for version updates. Security updates always target the default branch
versioning-strategy How npm, pip, uv, cargo, composer and others edit version ranges: auto, increase, increase-if-necessary, lockfile-only, widen
exclude-paths Glob patterns Dependabot should not scan
registries Private registries, with credentials taken from Dependabot secrets such as $

One option is gone: reviewers was removed in 2025. GitHub now assigns reviewers from your CODEOWNERS file.

Keep the noise down

Unconfigured version updates open one pull request per package per release. These settings matter most:

  • Group minor and patch updates. One weekly pull request per ecosystem is far easier to review than twenty.
  • Set a cooldown. A few days lets the rest of the world find a broken release before you install it, and it blunts the "malicious new version" style of supply-chain attack. Security fixes still arrive at once.
  • Use a slower schedule for low-risk ecosystems. GitHub Actions monthly is plenty for most sites.
  • Ignore majors you have decided to hold, and write down why in the commit.
  • Expect pauses on quiet repositories. Dependabot pauses version updates when nobody interacts with its pull requests for a long time.

Each pull request shows a compatibility score, an estimate of how often the same update passed CI in other public repositories. It is a useful hint, not a guarantee.

Security updates and transitive dependencies

Most alerts are not on packages you installed yourself. They are on a dependency of a dependency, which npm calls a transitive dependency. In our case all 55 were.

GitHub's documentation says what Dependabot can do about them:

  • npm: it updates whatever you declared, which may mean updating the parent package or removing a sub-dependency the parent no longer needs.
  • NuGet with PackageReference: it tries the parent; if that fails, it pins the transitive package directly.
  • Most other ecosystems: it cannot fix a transitive dependency when that would require updating the parent.

When no fix is possible, the alert shows an error instead of a pull request. You then have four choices:

  1. Update the parent yourself, if a newer parent pulls in the fixed version.

  2. Force the version. npm's overrides (Yarn's resolutions, pnpm's pnpm.overrides) pin a transitive package. A range key limits the override to the vulnerable versions:

    "overrides": {
      "undici@>=6.0.0 <6.29.1": "6.29.1"
    }
    

    Run your tests afterwards; you are now shipping a combination the parent's authors did not test.

  3. Remove the parent if you do not use it.

  4. Dismiss the alert with a reason, if the risk does not apply to you.

Auto-triage rules

Auto-triage rules act on alerts before anyone is notified:

  • GitHub presets, available everywhere. One dismisses low-impact npm alerts in development-only dependencies; it is on by default for public repositories and opt-in for private ones. Another dismisses malware alerts that flag every version of a package, which usually means an internal package shares a name with a malicious public one. It is off by default, for good reason.
  • Custom rules dismiss, reopen or open pull requests for alerts by severity, package name, CWE and other metadata. They are available on public repositories and, for private organization repositories, with GitHub Code Security.

Auto-dismissed alerts are not deleted. They close with the reason auto-dismiss, stay searchable, and reopen if the metadata changes, for example when a development dependency becomes a production one.

Automate it with GitHub Actions

Workflows triggered by Dependabot pull requests run with tighter rules:

  • Secrets: they read Dependabot secrets, a separate store from Actions secrets. A deploy-preview token stored only as an Actions secret is invisible to them.
  • Token: set the permissions your job needs explicitly.

The common pattern is to merge patch updates automatically once CI passes. This uses GitHub's fetch-metadata action (v3.1.0 as of this writing; pin it to a commit SHA in production):

name: Dependabot auto-merge
on: pull_request

permissions:
  contents: write
  pull-requests: write

jobs:
  automerge:
    runs-on: ubuntu-latest
    if: github.event.pull_request.user.login == 'dependabot[bot]'
    steps:
      - id: meta
        uses: dependabot/fetch-metadata@v3
        with:
          github-token: "$"
      - if: steps.meta.outputs.update-type == 'version-update:semver-patch'
        run: gh pr merge --auto --squash "$PR_URL"
        env:
          PR_URL: $
          GH_TOKEN: $

Two settings come first. Turn on Allow auto-merge in the repository's general settings, or --auto fails. Then turn on Require status checks to pass before merging for the branch: --auto waits only for required checks, so without any the pull request merges at once. If the branch uses a merge queue, GITHUB_TOKEN cannot add to it; use a GitHub App token.

To stop new vulnerable packages at the door, add GitHub's dependency-review-action (v5) to pull request checks. It fails a pull request that adds a dependency with a known advisory, before the alert ever exists. It works on public repositories; private ones need GitHub Code Security.

How we triaged 55 alerts

The alerts were on the private repository that builds our self-hosted workstation desktop, a container image that bundles command-line tools: AI coding assistants, the Netlify, Cloudflare and Vercel CLIs, MCP servers, Puppeteer and Lighthouse. This is the method, and it works on any repository.

1. Get the list in a form you can sort. The Security tab is fine for ten alerts. For fifty, use the API:

gh api --paginate "repos/OWNER/REPO/dependabot/alerts?state=open&per_page=100" \
  -q '.[] | [.number, .security_advisory.severity, .dependency.package.name,
            .dependency.manifest_path, .security_vulnerability.vulnerable_version_range,
            (.security_vulnerability.first_patched_version.identifier // "none")] | @tsv'

All 55 came from two lockfiles: 49 in the desktop's main tool list and 6 in a separate browser-automation bundle. Every one was a transitive dependency. Twenty-eight were the same HTTP client, undici, installed in five different versions by different tools.

2. Check that the lockfile is what actually ships. This was the surprise. The lockfile in Git was last changed on October 8. Our desktop updater bumps tool versions when it builds a release, so the desktop running since October 21 had a different lockfile that existed only inside the image. Dependabot was grading an old file. We copied the real lockfile out of the running container and matched every alert against it. All 55 still applied: the newer tools bundled the same vulnerable packages.

3. Find who pulls each package in. npm explain <package> shows the chain in a project you have installed. For a lockfile alone, a short script that walks package-lock.json does the same. The answers were specific: undici came from the Vercel CLI and from Wrangler's local Workers runtime, body-parser and toml from the Netlify CLI, node-forge from Netlify's image tooling.

4. Read the advisory for its precondition. Severity is only the score. The text says what an attacker needs:

Scroll to compare columns and rows when the table extends beyond the box.

Package Alerts What an attacker needs
undici 28 (10 high) You connect to a server they control, or your code passes its own certificate-check callback (the one that gets silently dropped)
body-parser, toml, fastify, qs, busboy, esbuild 10 Your local development server is running and they can send it requests
node-forge 1 high You verify their signatures with a low-exponent RSA key
zod 2 high You validate a huge array they supply
fast-uri, ip-address, deepmerge 14 Minor parsing issues

For command-line tools on a single-user desktop that nothing on the internet can connect to, none of these is a realistic path in. That is a judgment about our use, not about the packages; the same body-parser alert on a public API server would be urgent.

5. Simulate the fix before doing it. We copied the tool list, set every tool to its latest version, and resolved a fresh lockfile without installing anything:

npm install --package-lock-only --ignore-scripts

Matching the 55 alerts against that file gave the plan:

Scroll to compare columns and rows when the table extends beyond the box.

Action Alerts cleared
Rebuild with current tool versions 31
Drop the Vercel CLI, which we never signed in to 22 more
deepmerge: no patched version exists; low impact 2 to dismiss

6. Close the loop in Git. The lasting fix is to commit the lockfile the build actually uses, so Dependabot reports on what ships. Dismiss what remains with an honest reason; the API accepts fix_started, inaccurate, no_bandwidth, not_used and tolerable_risk:

gh api -X PATCH repos/OWNER/REPO/dependabot/alerts/55 \
  -f state=dismissed -f dismissed_reason=tolerable_risk \
  -f dismissed_comment="Low impact, no patched version; dev tool on a single-user desktop"

A short checklist

  • Turn on the dependency graph, alerts, security updates and grouped security updates on every repository.
  • Add dependabot.yml with grouped minor and patch updates, a cooldown, and a slower schedule for GitHub Actions.
  • Put reviewers in CODEOWNERS, not dependabot.yml.
  • Store any token a Dependabot pull request needs as a Dependabot secret.
  • Allow auto-merge and require status checks before you let anything merge itself.
  • Commit the lockfile your build really uses, or the alerts describe something else.
  • For each alert: is it the shipped version, who pulls it in, what does the attacker need, and does a parent update, an override or removing the tool fix it?
  • Dismiss with a reason, and write the reason so the next person can follow it.

Dependabot is very good at the first half of the job: knowing that a vulnerable version is present. The second half, deciding whether it matters, is still yours.

Dependabot also only sees your repository. To check what your live site actually serves to every visitor, such as outdated libraries, public source maps or exposed lockfiles, run the free Mega Security Analyzer on it.

← Back to Blog

Accessibility Options

Text Size
High Contrast
Reduce Motion
Reading Guide
Link Highlighting
Accessibility Statement

J.A. Watte is committed to ensuring digital accessibility for people with disabilities. This site conforms to WCAG 2.1 and 2.2 Level AA guidelines.

Measures Taken

  • Semantic HTML with proper heading hierarchy
  • ARIA labels and roles for interactive components
  • Color contrast ratios meeting WCAG AA (4.5:1)
  • Full keyboard navigation support
  • Skip navigation link
  • Visible focus indicators (3:1 contrast)
  • 44px minimum touch/click targets
  • Dark/light theme with system preference detection
  • Responsive design for all devices
  • Reduced motion support (CSS + toggle)
  • Text size customization (14px–20px)
  • Print stylesheet

Feedback

Contact: jwatte.com/contact

Full Accessibility Statement • Privacy Policy

Last updated: April 2026