On 14 February, nginx 1.25.4 shipped with advisories for two HTTP/3 bugs, and hours later its longest-serving developer announced freenginx, writing that F5 had decided to interfere with the project's security policy. This is about which parts of a dependency you actually hold, because the source is the part that comes with the tarball. The severity call, the disclosure clock and the build flags belong to somebody else, and those are what decide what your week looks like.
12 March 2024·6 min read·dependenciessecurity
The release came out first. nginx 1.25.4, dated 14 February 2024, with one security line in the changelog: a segmentation fault in a worker process while handling a specially crafted QUIC session, covering CVE-2024-24989 and CVE-2024-24990. The project's advisory page rates both as major, affecting 1.25.0 through 1.25.3, fixed in 1.25.4. That is a perfectly ordinary security release, and every organization running nginx got the same alert that afternoon.
Hours later, Maxim Dounin posted a message titled announcing freenginx.org. He wrote that "some new non-technical management at F5 recently decided that they know better how to run open source projects", and specifically that "they decided to interfere with security policy nginx uses for years, ignoring both the policy and developers' position". He was starting a project run by developers rather than corporate entities, with the stated goal of keeping development free from arbitrary corporate actions.
He also wrote the sentence that decides how much of this is about ownership: "they own the project, and can do anything with it, including doing marketing-motivated actions, ignoring developers position and community". That is a correct statement of the arrangement, offered without much complaint about its legitimacy. The company owns the trademark, the domain, the release infrastructure and the decision. Nothing improper had to happen for the outcome to be what it was.
So here is what the day is actually an example of, and it has nothing to do with web servers. When you depend on a corporate-stewarded open source project, the source is the part you genuinely have. The release process, the severity call, and the timeline on which a fix reaches you are held by someone else, and they are the parts that determine what your week looks like. The dependency you cannot fork in an afternoon is not the code. It is the vendor's definition of "critical".
The module is not compiled unless you ask for it. HTTP/3 in nginx lives in ngx_http_v3_module, and its documentation says plainly that the module is not built by default and has to be enabled with --with-http_v3_module at configure time. That is a decision made by whoever produced your binary, months before the bug was found. For most people that is a distribution maintainer or an image publisher, and the honest answer to "am I affected" starts with finding out what they chose.
And it does nothing until a listener asks for it. Even compiled in, the code is inert until a listen directive carries the quic parameter. So exposure requires two independent affirmative acts: somebody built with the flag, and somebody wrote the directive. Compare that with a bug in request parsing, which is reachable on every deployment that has ever served a byte, and the difference in how many operators have to move tonight is enormous.
The project labels the code experimental itself. The same documentation page says "The module is experimental, caveat emptor applies". That is not a hedge added later. It is a standing statement that this code is offered on different terms from the rest of the server, which is what "experimental" is for: it tells you the maintainers are not yet making the promises about it that they make about everything else.
And that sits oddly next to the advisory. Both facts live on the same domain. One page rates the two bugs major and assigns them CVE identifiers; another page tells you the code they are in is experimental and off by default. Neither statement is false. They simply come from two different judgments about what the project owes its users for code it has explicitly flagged as provisional, and only one of those judgments got to ship a release.
Dounin's message does not mention the 1.25.4 release. It does not mention HTTP/3, or either CVE, or any specific disagreement. It says the security policy was overridden, and it says nothing about which decision that referred to. The connection everyone drew, including me, comes from the two things landing within hours of each other on 14 February, plus the fact that the release fixed bugs in code the project's own documentation calls experimental. That is a strong coincidence and it is still a coincidence.
The narrower claim survives without the inference, though, and it is the one worth keeping. Somebody decided those bugs warranted a security release with identifiers attached, over the objection of a developer who had been maintaining the project for years, and that person was not chosen by the people running the software. Whatever the specifics, a severity judgment moved from the developers to the owner, and the owner is a company with a commercial product line adjacent to the free one.
That is the shape to take away rather than a verdict about anyone's motives. Severity is not a measurement. It is a decision made under a policy, by whoever the policy names, and when a project changes hands the policy is one of the assets that changes hands with it. You can read the source of a dependency in an afternoon. You cannot read who will be making that call next year.
Somebody else owns the severity scale and the right to set it. Every project that issues advisories has a rule for what qualifies, and those rules differ enormously in whether they cover experimental features, whether a crash in a worker counts, and whether a configuration nobody runs is in scope. That rule is the thing generating your alerts, and almost nobody reads it before adopting the dependency. It is usually one page, and it tells you more about your future Tuesdays than the code does.
The clock is entirely theirs. Disclosure timing, embargo length, whether downstream packagers get advance notice, and whether a fix ships alone or waits for the next scheduled release are all decided upstream. A fork gives you the ability to write your own patch. It does not give you the ability to know about the bug at the same time as the people who were told a week early, and that gap is where most of the real risk sits.
The artifact is usually somebody else again. The source is upstream, the binary is your distribution or your base image, and the two run on different schedules. When a release lands, what you are waiting for is not the commit. It is a rebuilt package with the flags your platform chose, through a pipeline you also do not control. A new project can publish source on day one and still be months from being something you can install the way you install things.
Know your build flags. The first question in every one of these events is whether the vulnerable code is even present in what you run, and for anything with optional modules the answer lives in the configure line of whoever produced your binary rather than in the advisory. Being able to answer that in minutes, from something you wrote down once, is the difference between a short conversation and an evening of everybody guessing at each other.
Read the project's security policy while nothing is on fire. It takes ten minutes and tells you what will be classified as an issue, on whose authority, and whether provisional code is in scope. If the policy is thin or absent, that is itself the finding: it means the severity of your future incidents will be decided ad hoc, by whoever holds the project when the bug arrives.
Then form your own view of severity anyway. An advisory is an input, not an instruction, and the same identifier means different things to two organizations with different configurations. A crash in a code path you do not compile is not the same event as one in the path every request takes, and an organization that cannot make that distinction internally will spend its emergency capacity on the wrong nights and have none left for the right one.
And be honest about what forking buys. It buys the ability to carry a patch, which matters. It does not buy the distribution, the packagers, the security list that people actually read, or the years of accumulated trust that make a project the default. Those took a long time to build and they do not transfer with a copy of the tree, which is why the fork is a check on behavior rather than an escape from it.
The claim needs narrowing, because as stated it proves too much. Most dependencies never have a day like this, corporate stewardship pays for a great deal of maintenance that volunteers were not going to do, and a company that funds full-time work on infrastructure everyone uses is doing something useful even when it makes a call its developers dislike. I would not run less software on this basis. I would run the same software with a clearer idea of what part of it I have.
What I keep returning to is how little of the dependency is the code. You can read all of it, patch all of it, vendor it, mirror it, and none of that touches the sentence that will actually cost you a night: someone you have never met deciding, under a policy you did not read, that a bug in a module you never compiled is worth an advisory. The source is the part that comes with the tarball. The rest arrives with the owner.