top of page

208 Dependabot Alerts to Zero in One Session. 17 Were Real. The Other 191 Were Lies I Was Telling Myself.

  • Writer: Patrick Duggan
    Patrick Duggan
  • 5 days ago
  • 9 min read

GitHub Dependabot says I have 208 open vulnerability alerts on enterprise-extraction-platform. 127 high. 73 moderate. 8 low. The number has been climbing for weeks and I have been ignoring it the way you ignore a check engine light when you know exactly what's wrong but don't have time to deal with it tonight.


Tonight I dealt with it. The number is now zero. 17 of those alerts were actual vulnerabilities in code I actually run. 191 of them were noise from code I do not run, generated by Dependabot scanning every package-lock.json in a monorepo regardless of whether the corresponding service has been deployed in months. The honest action on the 191 was not to fix them. It was to dismiss them with documented reasons and stop pretending they were a backlog.


This post is the methodology for telling those two piles apart and why the second pile is more dangerous than the first.


The Inflated Number Problem



Dependabot does not know which of your services are deployed. It does not know which of your scripts are vestigial. It does not know that you forked a microservice in November to test an idea, abandoned it in December, and have been carrying around its package-lock.json ever since. It just knows there is a lockfile and there is a CVE and the math says you have a problem.


The math is right and the conclusion is wrong. A vulnerability in code that does not run is not a vulnerability. It is a fact about a file. Treating it like a vulnerability has two costs. The first cost is that you do useless work patching dead code. The second cost, which is much worse, is that the inflated alert count desensitizes you to real alerts.


When your dashboard says 208 open alerts, you cannot triage by reading. You triage by feel, and feel says the same thing every time: tomorrow. When your dashboard says 17 open alerts in code you actually run, you read every one of them, and the next deploy has zero of them.


The transition from 208 to 17 is a triage decision. The transition from 17 to 0 is an engineering decision. Most teams confuse these. They try to do the engineering on all 208 and burn out, or they try to do the triage on all 208 and lose the actual real vulnerabilities in the noise. The right move is to do the triage first, find the real number, and then do the engineering on a list small enough to fit in your head.


The Triage



I pulled all 208 alerts via gh api. I grouped them by manifest path and unique package. The unique package count was 14. The unique manifest path count was 16. Most of the 208 was the same handful of packages showing up across multiple lockfiles in multiple variations.


undici alone accounted for 12 alerts: three CVEs across four manifests. picomatch accounted for 30 alerts because it appears in two major versions across half the monorepo. The headline number was big because Dependabot multiplies. The underlying reality was much smaller: 14 packages, several with already-published patches, most with caret ranges in their parent dependency declarations that just needed an npm update to refresh the lockfile.


So far so normal. Now the interesting part.


I asked the only person who knows which of those 16 manifests is actually in production. He told me four things very fast:


One: "Analytics is deployed." That's our root package.json — the analytics dashboard server that handles API traffic for 275 STIX feed consumers, the AIPM audit pipeline, the customer welcome system, all of it. Two alerts on that manifest, both high/medium for path-to-regexp.


Two: "Edgeshield is customer." That's dugganusa-edge-shield, the open-source Cloudflare Worker product. We treat it like a customer-facing artifact because it is one. It had zero Dependabot alerts because we'd already pinned it carefully a few weeks back during the axios supply chain attack.


Three: "Brain appears vestigial." That's microservices/analytics-dashboard/dashboard-brain — an early architectural attempt that got rolled into the main analytics dashboard months ago. The lockfile is still there, but no container ever pulls it. 21 alerts in code that has not been deployed since January.


Four: "Splitting 2x4, router, api-optimized, tank and fast path to its own repo — there's gold in them hills." That's the experimental microservices architecture from a year ago, the one that gave the Azure resource group cleansheet-2x4 its name. Four services. None deployed in months. About to be extracted to their own repository as a standalone product play. Approximately 80 of the 208 alerts live in those four directories.


That is the entire monorepo. Live surface: two manifests, 23 alerts. Vestigial surface: dashboard-brain, 21 alerts. Spinout surface: 4 services, ~80 alerts. Sunset surface: services/* extractor split, frontend-gateway, towelie, scripts/precog-sweep, ~80 alerts.


23 actual alerts. Out of 208. 11 percent of the dashboard number was the real backlog.


Fix the 23 (Surgically)



Start with the 2 root analytics alerts. Both were path-to-regexp 8.3.0, both fixed in 8.4.0. Path-to-regexp comes in transitively via express 5.2.1 → router 2.2.0, and router already declared path-to-regexp: ^8.0.0 in its dependency manifest. The caret means npm should prefer the latest 8.x, which is now 8.4.2. The lockfile was just stale.


npm update path-to-regexp from the project root. Took 985ms. Lockfile updated from 8.3.0 to 8.4.2. npm audit reports zero vulnerabilities on the root manifest. Two alerts closed with one transitive bump, no edits to package.json, no overrides hack, no Express version bump, no testing of unrelated code. Surgical.


Now the 21 alerts in dashboard-brain. Per the conversation, vestigial. Skip.


Now scripts/judge-dredd-agent. This is interesting. Judge Dredd is the pre-commit hook and CI gate that enforces our deployment laws. He runs on every commit locally. He runs on every PR/push in GitHub Actions via unified-governance.yml. He is, definitionally, the most security-sensitive script in the repo because if Judge Dredd is compromised, the gate that prevents bad commits from shipping is itself the attack surface. 15 alerts on him. Four underlying CVEs: fast-xml-parser (high), minimatch (three high), picomatch (high+medium), brace-expansion (medium).


All four have published patches. npm audit fix from inside scripts/judge-dredd-agent — done in 2 seconds. Smoke test: node scripts/judge-dredd-agent/cli.js status and cli.js review. Both work. The law itself was not broken by securing the law.


Live surface vulnerability count: zero. 23 down to zero in about ten minutes of actual code time.


Dismiss the 191 (Honestly)



This is the part that most teams do wrong. There are two wrong ways and one right way.


Wrong way one: Ignore the alerts and let them sit. The dashboard count grows. You stop reading it. Real alerts get buried. This is where everyone starts and where most everyone stays.


Wrong way two: Mass-dismiss as false_positive. GitHub gives you that option. It is dishonest because the vulnerabilities are not false positives. They are real CVEs in real code. The code just isn't deployed. Marking them as false positives lies to your future self and to your auditor. If you ever do deploy that code, you have no record of why those alerts were dismissed and you have to start the investigation over.


Right way: Dismiss as tolerable_risk with a written reason that includes the architectural justification, the path it lives in, and a forward-looking commitment about what happens when the situation changes.


I wrote one comment template and applied it to all 191 dismissed alerts via gh api: "2x4 spinout / sunset / vestigial path - see df731ba0." The commit hash points at the same session as the actual fixes, so the dismissals and the fixes are linked in the audit trail. The phrase "2x4 spinout / sunset / vestigial path" tells my future self exactly which of the three categories the manifest fell into. Any auditor reading this can follow the commit, see the architectural story, and verify that the alerts are tracked outside the live surface.


The 191 dismissals took about 90 seconds to fire (one PATCH call per alert via the GitHub Dependabot API, looped). I also pruned .github/dependabot.yml to stop monitoring the four spinout services and the sunset towelie service, so future Dependabot runs don't regenerate the noise. The judge-dredd-agent monitoring stayed in place because, as discussed, the law has to be the most secure thing in the repo.


Final state when GitHub Dependabot finished reconciling: 208 open → 0 open. 17 closed by code fix. 191 dismissed with documented reason. The methodology for which was which is right here in this blog post and right there in the dismissal comments.


The Spinout Note



The 80 alerts in router, tank-path, api-optimized-path, and fast-path are not getting fixed in this repo because those four services are about to be extracted to their own repository. The code is real. The vulnerabilities are real. They will be re-evaluated when the spinout repo is created — probably with a clean dependency baseline since most of those vulns would have been fixed by npm update anyway, the same way path-to-regexp was fixed for analytics.


I documented this decision in compliance evidence as a project memory: "2x4 architecture spinout — these manifests are moving, not dying. Don't refactor or delete them in enterprise-extraction-platform until the spinout actually happens." That memory is what tells me, three months from now when I see the same alerts re-appear because the lockfiles are still in tree, that the right move is patience, not panic.


Memory and dismissal comments are how you remember what past-you decided so future-you doesn't redo the work. They are, themselves, a form of compliance evidence. I file them anywhere I'd later look for context: the alert dismissal, the commit message, the project memory file, and the blog post. Belt, suspenders, lanyard, and a tattoo.


The Honest Test



Here's how you tell whether your security backlog is real or theater.


Pick the highest-severity open Dependabot alert in your repo right now. Read which package and which manifest. Now, without asking anyone, answer this question: Is that manifest deployed? If you can't answer in five seconds, your alert backlog is theater — not because the alerts are wrong, but because you have lost track of which of your code is real.


If the manifest is deployed, fix the alert today. It's a real vulnerability in code that runs.


If the manifest is not deployed, dismiss the alert with the architectural reason and prune Dependabot so it stops generating new ones for that path. Then, separately, decide what you're doing with that code: deleting it, archiving it, splitting it out, or restoring it to active service. Whatever you decide, the alert is no longer the urgent thing. The architectural decision is the urgent thing.


Most repos I've audited (including ours, until tonight) fail this test on the first alert. The fix is not technical. The fix is brutal honesty about what you ship.


Why This Matters For Compliance



We are pursuing GovRAMP Foundation Ready, SOC 2 Type 2 (currently 88%), and CMMC L2 (71/110 NIST SP 800-171 controls, on $600 a month of infrastructure). Every one of those frameworks has an explicit control around vulnerability management. None of them require zero open Dependabot alerts. All of them require a documented process for triaging vulnerabilities and a defensible justification for any vulnerability that remains open or dismissed.


The 191 dismissed alerts with consistent documented reasons are better compliance evidence than 191 unfixed alerts. They demonstrate that the team read the alert, made a decision, and recorded the rationale. That is the actual control: not zero alerts, but zero unexamined alerts. The 95% epistemic cap applies — we guarantee 5% of any compliance claim is bullshit. Pretending otherwise is the bigger compliance risk than admitting where the gaps are.


If a 3PAO assessor reads the dismissal comments on our 191 and asks, "Why did you decide tolerable_risk instead of fixing these?" — the answer is right there: the manifest is in a spinout/sunset/vestigial path, it is not deployed to production, the architectural disposition is documented in commit df731ba0 and in the memory file feedback-dependabot-dead-code.md. That answer survives the audit. Marking everything as false_positive does not.


What Tomorrow Looks Like



Tomorrow, the same Dependabot scan will run. It will find some new alerts because new CVEs land every day and our dependency tree is large. The only manifests Dependabot is now actively monitoring are the live ones plus judge-dredd-agent. New alerts will be small in number, real, and on code we actually run. The triage step (208 down to 23) collapses to zero next time. The engineering step (fix the real ones) is the only thing we have to do.


That is what a working vulnerability management process looks like. It is not zero alerts forever. It is a small, honest backlog that fits in your head and gets cleared in the same session it appears.


The boring architecture is the safe architecture. The boring vulnerability dashboard is the safe vulnerability dashboard. Tonight I made ours boring on purpose, and the act of making it boring was 90 percent honest paperwork and 10 percent actual code changes — which is the ratio you should expect, every time, if your shop is honest about which of its code is real.


Receipts



  • c08670e6 — security: bump path-to-regexp 8.3.0 → 8.4.2 (root analytics, 2 alerts closed)

  • df731ba0 — security: zero out Judge Dredd vulnerabilities, sunset towelie monitoring (15 alerts closed, 4 underlying CVEs)

  • 191 dismissals scoped to: services/*, frontend-gateway, scripts/precog-sweep, microservices/{router,tank-path,fast-path,api-optimized-path,towelie,analytics-dashboard/dashboard-brain}

  • .github/dependabot.yml pruned: removed monitoring for four 2x4 spinout services + towelie

  • Final state: enterprise-extraction-platform open Dependabot alerts: zero


The whole sequence took about 40 minutes from "let me actually look at this number" to "the number is zero." Most of that was reading. The actual code change was a single npm update, an npm audit fix, and a YAML edit. The hard part was not the engineering. The hard part was the honesty about which of our code matters.


If your alert dashboard is north of 100 and you have not looked at it in a week, your backlog is theater. Spend 40 minutes tonight separating the real from the noise. The number that comes out the other end will tell you something true about your shop. Most likely it will be much smaller than you fear and much more fixable than you've been telling yourself.


The check engine light goes off. The car was always fine. The dashboard was lying to you, because you let it.





Her name was Renee Nicole Good.


His name was Alex Jeffery Pretti.

 
 
 

Comments

Rated 0 out of 5 stars.
No ratings yet

Add a rating
bottom of page