243 Dependabot alerts in 12 weeks. Zero needed fixing today.

Illustration of two hands sorting a pile of alert cards into three desk trays labeled Today, Schedule, and Later. The Today tray is empty, Schedule holds a small stack, and Later is heaped full.

Dependabot opened 243 alerts on three of our production repos between mid-July and early October. It rated 166 of them Critical or High. Taken at face value, that looks like a lot of high-priority work for a small engineering team.

Our alert triage agent scored every open alert for risk, every Tuesday, for twelve weeks. It returned zero FIX_TODAY verdicts. Not one alert in those twelve weeks had all three things that make a fix urgent: evidence of exploitation, a reachable code path, and high impact.

Below is where those alerts went, the handful that still needed action, and how few pull requests and how little engineer attention it took to manage k9's vulnerable dependency risk.

The setup

Every Tuesday a Claude Code routine runs against each repo. It pulls the open Dependabot alerts, analyzes whether the vulnerable code is reachable from how the service actually runs, and sends each alert to k9's score_risk tool. The agent analyzes reachability. k9 scores. The score combines CISA and VulnCheck KEV, FIRST EPSS, the reachability evidence, and the asset context we declared in .k9security/risk-context.yaml, and returns one of four verdicts:

  • FIX_TODAY: fix now. Exploit evidence, a reachable code path, and high impact are all present.
  • REVIEW: an engineer investigates before deciding. The evidence is too thin to support any fix-or-defer call.
  • SCHEDULE: plan a fix. Two of the three are present, or a policy floor applies.
  • DEFER: no action now. One or none is present, and no floor applies.

The routine publishes a report, opens a fix PR when one is warranted and the tests pass, and files a Jira issue with the counts. That gives us twelve weeks of weekly records for three repos:

  • k9-frontend-app: the Django web app customers log into
  • k9-mcp-server: the public MCP server that scores alerts
  • k9-backend: the Serverless IAM access analyzer for AWS and Azure

One exclusion to mention: k9-mcp-server carries deliberately vulnerable projects under evals/ that we use to test the scoring. Dependabot raised 73 alerts on those. They are test fixtures, not production, so they are not counted in the 243.

Twelve weeks, three repos

Repo Alerts raised Critical or High Runs with nothing open Runs with only DEFER Runs with a SCHEDULE
k9-frontend-app 49 24 4 1 7
k9-mcp-server 137 107 3 4 5
k9-backend 57 35 4 5 3
Total 243 166 11 10 15

There were thirty-six triage runs over twelve weeks. They yielded zero FIX_TODAY alert verdicts. There were zero REVIEW verdicts in the 33 runs since the REVIEW verdict was added. Twenty-one of the 36 runs ended with nothing to schedule at all.

Volume did not trend consistently. It spiked, irregularly. Six of the twelve weeks brought fewer than 10 new alerts across all three repos. A repo can go from no new alerts to fifty in a week, which may interrupt your planned project work.

The week ending October 6 brought 87 alerts across our repos, and 51 of them landed on k9-mcp-server. Thirty-five were in CDK build lockfiles. Sixteen were in the running service: 13 PyJWT alerts and 3 urllib3 alerts. That run scheduled the 16, deferred the build-tooling alerts, and opened one PR that clears 15 of the 16.

Stacked bar chart of new Dependabot alerts per weekly triage run on k9-frontend-app, k9-mcp-server, and k9-backend, Jul 21 to Oct 6, 2026: 6, 39, 26, 30, 8, 3, 0, 21, 16, 6, 1, and 87 alerts. The FIX_TODAY row beneath shows 0 for every week.

Where 166 Critical and High alerts went

More than three-quarters of the 166 Critical and High alerts were in code that never runs in production. 129 of those alerts came from build and deploy tooling: 95 in k9-mcp-server's CDK lockfiles, 32 in k9-backend's Serverless Framework deployment tooling, and 2 in k9-frontend-app's dev-only npm packages. Build tooling processes our own templates. An attacker cannot send it input. The triage runs deferred every one of them.

The ones that did schedule are more instructive. Take anyio CVE-2026-63374, rated Critical, which hit all three repos in the same week. On September 22 the agent traced the vulnerable code path in each repo and found the service does not reach it. The Reachable Risk scoring rubric scheduled the fix anyway.

That is deliberate. When an alert sits on a public, business-critical service, the rubric applies a floor: it schedules the fix even when the agent determined the code is not reachable, because a reachability call can be wrong and the asset is too important to bet on it. The floor provides a safety margin, and the verdict explains it that way, so a reviewer can see exactly why an unreachable Critical still got a ticket. Nearly every SCHEDULE in the 36 runs came from that floor.

Two alerts were reachable:

  • djangorestframework CVE-2026-73228 (September 8): the agent traced it through k9-frontend-app's write endpoints. Not in KEV. SCHEDULE.
  • PyJWT CVE-2026-101917 (October 6): a JWKS amplification issue the MCP server's token verification can reach. SCHEDULE, in the PR mentioned above.

One alert across all 36 runs was in the KEV catalog: Pillow CVE-2023-4863, in one of the deliberately vulnerable eval fixtures. A KEV listing is exploit evidence, so the KEV floor guarantees at least a SCHEDULE, and it fired as designed. It stopped short of FIX_TODAY because the fixture never deploys: nothing reaches it, and nothing depends on it.

Severity said 166 alerts were Critical or High. The reachability and exploitation evidence said none needed fixing that day, a small set needed a scheduled fix, and the rest could wait for routine upgrades.

Patching by CVSS severity would have put all 166 alerts on the remediation SLA clock, 54 of them in the week ending October 6 alone. And most of those alerts were in build tooling an attacker cannot reach. Patching based on assessed risk is a much smaller workload and a calmer week, and the pull requests show how much smaller.

16 pull requests instead of 67

When you turn on Dependabot security updates, it defaults to opening one pull request per vulnerable package per manifest, whenever a fixed version exists. For these 243 alerts, that is at least 67 PRs in twelve weeks. Thirty of them would bump build-only packages in k9-mcp-server's five CDK lockfiles. Every one of them lands in someone's review queue, needs a green build, and gets merged or closed by hand.

We know what that feels like because we ran it. We turned security updates off on k9-backend and k9-mcp-server because the PRs were unprioritized noise. On k9-backend, Dependabot opened 7 PRs in late July and early August. We merged 1 and closed 6.

I missed k9-frontend-app, and it turned into a natural experiment. Dependabot opened 6 PRs there for 7 vulnerable npm packages, 3 of them dev-only tooling. We merged 5 because they had already pulled our attention, and closed 1. But the triage never scheduled any of those packages.

The triage process produced 16 security PRs across the three repos: 9 in k9-frontend-app, 3 in k9-mcp-server, and 4 in k9-backend. One more fix went straight to main from a Claude Code session. The loop is simple. The weekly run files a Jira issue with the verdicts. On Triage Tuesday an engineer reads it, opens a terminal, and finishes the fix with the same agent and the same scoring.

The PRs are fewer because DEFER alerts get no PR at all. They wait for routine upgrades and enhancements that our applications need. Four of the 16 also bundle two or three packages that Dependabot would have split into separate PRs. The October 6 PR on k9-mcp-server bumped PyJWT and urllib3 together and cleared 15 of the 16 alerts that run scheduled, with the verdict and the reachability evidence for each alert in the report it links.

Sixty-seven PRs is the cost of treating every alert as work based on its externally defined severity. Sixteen PRs is the cost of treating the alerts that carry actual risk in your software deployment as work, while writing down why the rest can wait.

The customer-installable version of this triage runs as a GitHub Action in your own CI. It opens a PR only for a fix worth doing, only when your tests pass on the bumped branch, and it never merges on its own.

The 7-minute walkthrough below runs the action on a test repo with 9 open Dependabot alerts: the triage report, the one tested fix PR it opened, and the risk-context file that drives the scoring.

What this means if you own the alerts

Most companies with fewer than 500 people do not have a vulnerability management team. Someone on the engineering team owns the Dependabot tab by default, and severity tells them 166 alerts deserve attention.

Twelve weeks of our own data says the work is much smaller than that, and different. The job is to show, alert by alert, which alerts need your attention and which can wait. With the evidence written down, a reviewer can decide the next step with confidence, and check the work when they want to.

That is what Reachable Risk does. Reachable Risk Professional is $10 a month for 1,000 alerts, with a 7-day trial and no procurement cycle. Point your agent at your repo and see what your own backlog looks like when it is scored.