
This week the React team shipped emergency releases, 19.2.8, 19.1.9, and 19.0.8, to fix a denial-of-service bug in Server Function endpoints. If you run Server Functions in production, that is a patch-tonight situation.
Here is the thing. My site does not use Server Functions, and it still runs React 18 on a Vite single page app, so this particular bug never touched me. I could have found that out by panicking and reading the changelog. Instead I found it out in about ten seconds, because the boring machinery I set up months ago already had an answer. That machinery is the actual point of this post, not the bug.
The problem with "keep your dependencies updated"
Everyone says to keep dependencies current. Almost nobody wants to do it by hand. On a side project it is worse, because there is no team, no on-call, no one whose job it is to notice that a package you have never heard of shipped a fix for something scary.
So the goal is not "update dependencies." The goal is "make the project tell me when something matters, and make it impossible to merge something unsafe." Two pieces do that for me.
Piece one: Dependabot opens the PRs so I don't chase them
I let Dependabot watch every ecosystem the repo uses and open grouped pull requests once a week. Here is the part of my config that handles the frontend:
# .github/dependabot.yml
- package-ecosystem: npm
directory: /apps/frontend
schedule:
interval: weekly
groups:
# Patch/minor bumps are low-risk, batch them into one PR to cut noise.
minor-and-patch:
update-types: [minor, patch]
# Majors land on their own so breaking changes get reviewed alone.
major:
update-types: [major]
The grouping is the trick that makes this survivable. Ten separate patch bumps in ten separate PRs is noise, and noise gets ignored, and ignored is how you end up three years behind. So the small safe stuff gets batched into one PR I can glance at and approve. A major version bump, the kind that actually breaks things, lands on its own so I have to look at it on purpose. I do the same for my Go modules, my Dockerfiles, and my GitHub Actions.
Piece two: CI refuses to merge anything vulnerable
Dependabot only opens the door. What stops a bad merge is that my CI treats a known vulnerability as a hard failure, not a warning. On the frontend that is one line:
npm audit --audit-level=high
On the backend it is govulncheck ./... plus gosec. If any of them find something at the level I care about, the build goes red and the PR cannot merge. Not "cannot merge without a warning." Cannot merge.
This is why the Vite migration mattered more than it looked. Moving off Create React App took my dependency count from over 1500 packages to around 780 and dropped npm audit to zero. Once it was zero, I could make it a gate. You cannot gate on "zero vulnerabilities" if you are starting from forty of them, so the cleanup and the rule go together.
What this actually buys me
When a React CVE lands, I am not the person refreshing Hacker News to find out if I am exposed. Either a red build tells me, or a Dependabot PR shows up with the fix already applied, or the whole thing sails past because it never applied to my setup in the first place. This week it was the third one.
The unglamorous truth is that security on a small project is mostly automation you set up once and then forget. Spend a weekend wiring up Dependabot and one audit gate. After that, the scary headlines turn into a PR you approve over coffee, or a shrug.
Until then, see you at the top!