
This week the React team shipped emergency patches across its 19.0, 19.1, and 19.2 release lines to fix a denial-of-service bug in Server Function endpoints. The short version of the vulnerability: an unauthenticated attacker sends specially crafted HTTP requests to a Server Function, and the server burns CPU for up to a minute trying to handle them. If you run Server Functions in production, that is a patch-tonight situation. It is also not the first time. This was the latest in a recurring series of React Server Components advisories, which turns out to be part of the point.
Here is the thing. My site does not use Server Functions. It is a React 18 single page app built with Vite, and it does not ship the Server Components runtime at all, so this bug never touched me. I could have found that out by panicking and reading the changelog line by line. Instead I knew in about ten seconds, because the boring machinery I set up months ago already had the answer ready. That machinery, not the bug, is the actual point of this post.
How I knew in ten seconds
The vulnerability lives in one specific place: the server-side handling of React Server Functions. My frontend does not have a server side. It is static files built by Vite and served from a CDN, talking to a separate Go API. There is no React server runtime in my stack to attack. Confirming that took one glance at how the site is built, because I already knew my architecture did not include the thing the CVE targets.
That is the quiet advantage of understanding your own stack instead of assembling it from templates. When a scary headline lands, the first question is always "am I exposed," and if you know what you actually ship, that question has a fast answer. Half the panic around security news is really just not knowing your own dependencies.
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, and no one whose job it is to notice that a package you have never heard of shipped a fix for something serious.
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=highOn 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 1,500 packages to around 780 and got npm audit down to zero. Zero is not a number you set once, though. New advisories land against packages you already have, so the gate is not a promise that the count is always zero. It is a promise that the build fails the moment it is not, and I patch it back to zero before anything ships.
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. One of three things happens. A red build tells me and blocks the merge. 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, and the fact that it is a recurring series of Server Components bugs does not change that, because none of them reach a stack that does not run Server Components.
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 real audit gate. After that, the scary headlines turn into a PR you approve over coffee, or a shrug.