Secure the PipelineSCAStep 13 of 31

Automate Dependency Fixes with Pull Requests

difficulty beginnerhands-on 25 min hands-on

42% complete

prereqs · git-and-github

concepts · remediation vs detection · grouped updates · compatibility scores · auto-merge policy · breaking upgrades

The SCA lessons all answered the same question — which of your dependencies are vulnerable — and left you with a list. Trivy found 54 CVEs, Snyk 63, and every one of Snyk's was marked upgradable. A list of upgradable vulnerabilities is not a fix. Somebody still has to bump each version, open a pull request, and check nothing broke.

Dependabot is the tool that does that part. It is not a scanner — it is the thing that acts on the scanner's output. This lesson turns it on against the Range and reads the seven real pull requests it opened.

Verified on GitHub against the public Range repo. Every pull request below is one Dependabot actually raised.

Step 1 — One config file

Dependabot is enabled by committing a single file. No action, no workflow, no service:

yaml
# .github/dependabot.yml
version: 2
updates:
  - package-ecosystem: npm
    directory: /app
    schedule:
      interval: weekly
    open-pull-requests-limit: 10
    groups:
      npm-minor-and-patch:
        update-types: [minor, patch]

  - package-ecosystem: docker
    directory: /docker
    schedule: { interval: weekly }

  - package-ecosystem: github-actions
    directory: /
    schedule: { interval: weekly }

Three ecosystems, because the Range has three kinds of dependency: npm packages in app/, the base image in docker/Dockerfile, and the action versions in the workflows themselves. Most teams configure only npm and forget the other two — then wonder why their base image is three years stale. Dependabot updates whatever you point it at.

Step 2 — What it opened

Within minutes of the config landing on main, Dependabot had raised seven pull requests:

text
#7  deps: bump axios from 0.21.0 to 1.19.0 in /app
#6  deps: bump express from 4.16.0 to 5.2.1 in /app
#5  deps: bump jsonwebtoken from 8.1.0 to 9.0.3 in /app
#4  deps: bump ejs from 2.5.7 to 6.0.1 in /app
#3  deps: bump the npm-minor-and-patch group in /app with 3 updates
#2  Bump node from 18 to 26 in /docker
#1  Bump actions/checkout from 4 to 7

These are the same packages the SCA lessons flagged — axios (11 HIGH in Trivy), ejs (the CRITICAL CVE-2022-29078), lodash, minimist. Detection said "these are vulnerable". Dependabot turned each one into a reviewable pull request with the upgrade already staged. That is the difference between knowing and fixing.

GitHub pull requests tab for the Range showing seven open Dependabot PRs — axios, express, jsonwebtoken and ejs majors in /app, a grouped npm-minor-and-patch PR, node 18 to 26 in /docker, and actions/checkout 4 to 7 — each opened by dependabot

That is the real pull-requests tab, minutes after the config landed: seven PRs, every one opened by dependabot, spanning all three ecosystems.

Step 3 — The grouping worked

Look at #3: one pull request titled "bump the npm-minor-and-patch group with 3 updates"lodash 4.17.4 → 4.18.1, minimist 1.2.0 → 1.2.8, and sql.js, all in a single PR.

That is the groups: rule from the config doing its job. Without it, those would have been three separate pull requests, and a real repository with fifty dependencies generates a pull request storm that trains everyone to ignore the Dependabot label.

Group the low-risk, review the high-risk. Minor and patch bumps are where the automation earns its keep — they rarely break anything, so batching them into one PR (or auto-merging them, below) keeps the noise down. The majors came as individual pull requests, exactly as they should, because each one needs a human.

Step 4 — The upgrades that break production

Four of the seven are major version bumps, and two of those are the reason you cannot let Dependabot merge on its own:

  • #6 express 4.16.0 → 5.2.1 — a major framework upgrade. Express 5 changed routing, removed deprecated methods, and altered how req.param and the query parser behave. An app written for Express 4 may not start on Express 5 without changes.
  • #2 node 18 → 26 — eight major versions of the Node runtime in one jump. This is the same upgrade the Docker Scout lesson recommended for a completely different reason: Scout found that moving off node:18 removes 131 high and 5 critical CVEs. Dependabot proposes it as a version bump; Scout told you why it is worth the risk. Together they are the whole argument — the vulnerability case and the mechanism to act on it.

Each pull request carries two things that make the human review possible without leaving GitHub:

  • A Dependabot compatibility score — a badge showing what percentage of other public repos had passing CI after the same bump. Not a guarantee, but a strong prior: a major with a low score is a "block out an afternoon" upgrade.
  • The release notes and changelog, inlined. Express 5's PR body includes the breaking-change notes directly, so you read what changed without hunting for it.

This is the "upgrade that breaks production" in concrete form. node 18 → 26 and express 4 → 5 are both correct upgrades — more secure, supported — and both will break a build that merges them blind. The value of Dependabot is that it does the tedious 80% (find, bump, stage, document); the 20% it cannot do is decide whether your app survives the change. That 20% is why the next step matters.

Step 5 — Auto-merge, without being reckless

The temptation with seven PRs is to auto-merge them all. Do that and the express and node bumps take down production the first week. The safe pattern is narrow auto-merge scoped to exactly the updates that do not break things:

yaml
# .github/workflows/dependabot-auto-merge.yml
name: dependabot-auto-merge
on: pull_request

permissions:
  contents: write
  pull-requests: write

jobs:
  auto-merge:
    runs-on: ubuntu-latest
    if: github.actor == 'dependabot[bot]'
    steps:
      - uses: dependabot/fetch-metadata@v2
        id: meta
      - name: Auto-merge patch and minor only
        # never major — those need a human
        if: steps.meta.outputs.update-type != 'version-update:semver-major'
        run: gh pr merge --auto --squash "$PR_URL"
        env:
          PR_URL: ${{ github.event.pull_request.html_url }}
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Two guardrails make this safe rather than reckless:

  1. update-type != semver-major — patch and minor auto-merge; majors wait for a person. The express 4→5 PR would not auto-merge under this rule, which is the entire point.
  2. --auto merges only after required status checks pass. If your test suite is green, the bump is safe; if it is red, the PR sits open. Your auto-merge is exactly as trustworthy as your CI. A team with a weak test suite should not auto-merge anything, and that is a statement about the tests, not about Dependabot.

On this lab specifically, auto-merge stays off entirely. The Range's old dependencies are the teaching material — the whole point is that they are vulnerable — so the config here proposes upgrades and never merges them. On a real application, patch-and-minor auto-merge behind good tests is one of the highest-leverage things you can turn on.

Step 6 — Where Dependabot sits in the stack

It closes the loop the scanners open:

Stage Tool Output
Detect Trivy / Snyk "these dependencies are vulnerable"
Remediate Dependabot a pull request per fix, staged and documented
Gate Trivy in CI "nothing vulnerable merges"

Dependabot is not competing with Trivy or Snyk — it is the middle column they were missing. And there is a second thing GitHub does here worth enabling alongside it: Dependabot security updates, a separate toggle in the repository's security settings, which raises out-of-band PRs the moment a new advisory affects a dependency you use, instead of waiting for the weekly schedule. Version updates (this lesson) keep you current; security updates react to disclosures.

The arrangement that works: Trivy gates the pull request, Dependabot opens the fixes, security updates catch the urgent ones, and auto-merge handles the safe majority — leaving humans to review only the handful of major bumps that could actually break something.

What you learned

  • Dependabot is remediation, not detection. It opens the pull request that fixes what the scanners found — the column Trivy and Snyk do not have.
  • One config file turned it on for three ecosystems and it raised seven real PRs in minutes, against the same dependencies the SCA lessons flagged.
  • Grouping works: the groups: rule batched three minor/patch bumps into one PR (#3); the four majors came individually, as they should.
  • Four were major bumps. express 4→5 and node 18→26 are correct upgrades that would break a blind merge — the same node:26 the Docker Scout lesson recommended for −131 highs.
  • Each PR carries a compatibility score and inline release notes, so review happens in GitHub.
  • Auto-merge patch and minor behind --auto; never majors. Your auto-merge is only as safe as your CI. On this lab it stays off, because the vulnerable deps are the lesson.
  • Pair it with Dependabot security updates for out-of-band advisory fixes, and let Trivy keep gating.