//nefariousplan

Design Debt Driver

A component whose bug-class keeps recurring. Patches address symptoms; the design holds the primitive.

Some components are not unlucky. Some components are fertile.

A normal piece of software accumulates bugs over time, gets them patched, gets audited, and the bug rate tapers. The bugs are incidental: a null dereference here, a logic flaw there, each fix reducing the total. A design-debt-driver does not behave this way. It produces bugs of the SAME CLASS, year after year, decade after decade, with different triggers and different CVEs but the same underlying shape. Each patch closes the specific instance. The instance count keeps going up.

When you see that rhythm, the maintenance team is not the story. The design is.

Mechanism

Every component has a failure profile. Some components fail by arithmetic: a simple bug, one off-by-one, fix it and move on. Some components fail by misuse: the user configured it wrong, the integration was sloppy, the surrounding context set up an unsafe state. Design-debt-drivers fail by class: the component's architecture is a fertile substrate for a specific bug family, and the family reproduces.

The mechanism is compositional. Most real bugs come from the interaction of several design decisions that each, in isolation, looked reasonable. A parser that accepts optional fields. A security gate that fails open for forward compatibility. An administrative interface exposed on the same TCP port as the data interface. A binary format with length fields that the parser trusts. Each of those choices is defensible. Together they produce a pattern where exploit primitives are one new feature away.

The substrate does not fix itself when an instance is patched. The next maintainer adds a new option, or a new caller uses an existing option in a new context, and the same class resurfaces with a different entry point. Defenders track the CVE list. Attackers track the substrate. Attackers win because they are right about where to look.

Exhibits

CVE-2026-33937: Handlebars Trusts Its Own AST. Handlebars is a design-debt-driver in its purest form: a public API surface (compile, partials, decorators, the CLI precompiler, runtime helpers) that converts caller-influenced inputs into JavaScript the runtime executes, with every surface trusting upstream invariants no caller is contractually obliged to maintain. Prior instances span seven years (CVE-2019-19919 lookupProperty pollution, CVE-2019-20920 Object.__defineGetter__ RCE, CVE-2021-23369 compileToCallback strict-mode bypass, CVE-2021-23383 __proto__ pollution in compile). The March 2026 family lands in a single commit that closes eight advisories at once, three of which share the literal phrase "JavaScript Injection via AST Type Confusion." The 67-line patch validates three node fields. The substrate (compile-accepts-string-or-AST, partials accept objects, runtime helpers compose with library internals) is unchanged. The next family is the next researcher finding the next surface.

CVE-2026-42945: The Other Half of the 2012 Patch. NGINX's script engine is a clean exhibit of the pattern. Two parallel flags, is_args and quote, govern the same escape-budget predicate in ngx_http_script_copy_capture_len_code and ngx_http_script_copy_capture_code. The two flags have asymmetric reset policies: quote has been zeroed at the end of ngx_http_script_regex_end_code since release 0.1.29 in 2005, and is_args was not. The asymmetry produced the same buffer-overrun twice. In 2012, ticket #162 closed an is_args leak from a sub-engine. In 2026, CVE-2026-42945 closes the leak from the main engine. Both fixes are one line. Both reuse the same opening clause in their commit messages. Fourteen years separate them. The driver is the predicate's fan-in over flags whose reset hooks do not all live in the same block.

CVE-2026-34159: The Deserializer Three CVEs Have Not Patched. llama.cpp's RPC backend has produced three CVEs whose root the GHSA author groups under one sentence: deserialize_tensor() does not validate tensor->data when buffer=0. CVE-2024-42478 reached the deserializer via GET_TENSOR; the 2024 fix added bounds-checking in the get_tensor handler. CVE-2024-42479 reached it via SET_TENSOR; the 2024 fix added bounds-checking in the set_tensor handler. CVE-2026-34159 reached it via GRAPH_COMPUTE; the 2026 fix added a null-buffer check in create_node. Three patches in three different functions, each closing the call site that produced its CVE. The deserializer that returns the unsafe tensor is the same in 2026 as it was before any of the three patches landed. The next RPC command that takes a tensor on the wire and uses the result's data field is its own opportunity for the same primitive to surface as a fourth CVE.

CVE-2026-23918: m->spurge Was an h2_ihash. The Array That Replaced It Kept the Assertion, Not the Dedup.. mod_http2's h2_mplx is the substrate. Streams are tracked across three containers (m->streams, m->shold, m->spurge) with hand-coded transitions across at least four code paths, and the invariants between containers are enforced as ap_assert panics at consumers rather than as constraints at producers. CVE-2023-44487 (Rapid Reset), CVE-2023-45802 (the Apache-specific Rapid Reset memory-release variant), and CVE-2026-23918 are three stream-lifecycle CVEs in twenty-six months from the same machinery. The 2.4.67 patch enforces one invariant at one producer (the add_for_purge dedup) and leaves the assertion that named the invariant in 2021 in place at the consumer. Each patch closes the instance that produced its CVE. The substrate that produces the next one — many code paths transitioning the same h2_stream pointer between three containers under invariants enforced as panics — is unchanged.

CVE-2023-50094: The CVE Number Is a Drawer, and reNgine Put Seven Bugs In It. reNgine is a design-debt-driver in its clearest form. Seven command injection fixes in three years across seven different files, each following the same shape: user-controlled string concatenated into a shell command, passed to subprocess with shell=True or the split-and-exec equivalent. The central run_command utility accepts a raw string and a shell=True flag; seventeen call sites in tasks.py pass that flag today. Each patch closes its one call site. The substrate that makes the next call site exploitable ships unchanged. The CVE count under-counts the class because six of the seven patches never got a CVE number; the only one that did, CVE-2023-50094, has absorbed unrelated bugs from other reporters, so the ID no longer points at a single fault. Defenders subscribe to the bug class by running reNgine.

CVE-2026-39987: The Only WebSocket in Marimo Without an Auth Check Was the One That Forks a Shell. Starlette plus WebSockets is the substrate. Starlette's AuthenticationMiddleware populates scope["user"] on every connection but does not reject unauthenticated ones; HTTP endpoints opt into rejection through the @requires(...) decorator, and WebSocket endpoints have no equivalent. Marimo's maintainer diagnosed this in a September 2025 commit message and patched /ws and /ws_sync with a seven-line block. The /terminal/ws handler in the file next door got the identical seven-line block 211 days later, after it became CVE-2026-39987 and CISA KEV-listed. Five days after that, the same author patched the LSP ProxyMiddleware for the same class. Three instances, three fixes, one CVE. The CVE landed on the instance that happened to call pty.fork().

CVE-2024-45409: The SignatureValue Verified. The DigestValue Compared Was Not in the Signature.. Ruby-SAML's signature verifier has now produced authentication bypasses through four distinct mechanisms: duplicate-element wrapping (CVE-2016-5697), XML comment truncation (CVE-2017-11428), XPath root-anchoring (CVE-2024-45409), and parser differential between REXML and Nokogiri (CVE-2025-25291, CVE-2025-25292). The 1.18.0 fix landed six months after the 1.17.0 fix for this CVE. Each patch closed a specific instance. The architecture, a single validation method built on two XML parsers with XPath queries sprinkled through canonicalize-hash-compare-accept, is unchanged. The substrate that makes new variants exploitable holds across every fix.

CVE-2025-27407: graphql-ruby Loaded the Schema by Compiling It. The CVE-2025-27407 patch is a design-debt-driver admission written into a lint config. Two class_eval-with-HEREDOC sites had to be rewritten in one diff (InputObject#argument and BuildFromDefinition#define_field_resolve_method); both were instances of the same pleasant-to-write Ruby idiom for defining a method from a name. Robert Mosolgo's third change in the patch is a new RuboCop rule, cop/development/no_eval_cop.rb, that bans class_eval, module_eval, and instance_eval from the codebase regardless of arguments. The cop does not check whether the call passes a string or a block; it bans the method names entirely, on the read that any future contributor reaching for the same idiom will produce the same class of bug. Point-fixing the two known sites would have closed this CVE; banning the family is the maintainer's verdict that the design produces this bug class faster than review catches it.

Dirty Frag: the patched half is the half Ubuntu already mitigated. crypto_authenc_esn_decrypt writes attacker-controlled bytes into whatever the destination scatterlist resolves to, with no awareness of whether the page is read-only, page-cached, or backing a setuid binary. Copy Fail (CVE-2026-31431) reaches this sink via algif_aead. Dirty Frag (CVE-2026-43284) reaches it via MSG_SPLICE_PAGES plus xfrm. The fix is split across esp4.c, esp6.c, ip_output.c, and ip6_output.c precisely because the sink itself encodes no policy and every caller must encode it separately.

CVE-2026-40261: The Injection Is in syncCodeBase, Not generateP4Command. CVE-2021-29472 was command injection in Composer's Hg VCS driver via unsanitized URL strings. The 2021 fix hardened the Hg driver and "adjacent VCS drivers." The Perforce driver kept string-based shell command construction for another five years. Composer 2.9.6, which closes the Perforce driver CVEs, also separately hardens branch-name handling across git, hg, and fossil. The VCS driver architecture distributes shell command construction across independent files; the bug class reproduces driver by driver. This is the third distinct instance of the class across Composer's VCS drivers, with the Perforce driver reaching 2026 carrying the same primitive the Hg driver shipped with in 2021.

CVE-2026-34486: EncryptInterceptor Only Encrypts Messages That Survive Decryption. EncryptInterceptor has now produced a padding oracle (CVE-2026-29146) and a deserialization bypass (CVE-2026-34486) from the same messageReceived() method. The bypass was introduced by the padding oracle fix: refactoring the encryption manager relocated super.messageReceived() outside the try block. One security patch, one new CVE in the same file, same method, same code style. The component whose explicit job is securing cluster traffic has generated two consecutive CVEs from its primary security method. The patches closed the instances. The exception-handler style that produced both is unchanged.

CLFS: Ransomware's Favorite Kernel Driver. The Common Log File System driver has been the source of Windows kernel LPE CVEs for years, consistently, with the same general class (parser confusion on a binary log format that was designed for internal OS consumption and then exposed to user-supplied input). Ransomware crews maintain long lists of CLFS primitives because they know the pipeline: new CLFS CVE, ninety days to weaponization, fresh LPE for the next six months. Microsoft patches them. The next one shows up. The driver's parsing surface is too big and too untrusted for the patch-as-you-find-it model to converge.

Boundaries

Not every frequent-CVE component is a design-debt-driver. Popular libraries attract attention, which finds more bugs. If the CVEs are varied (one memory safety, one logic error, one auth bypass, one side channel), that is healthy security research, not pattern reproduction. Design-debt-driver requires the same CLASS showing up again.

Not every legacy codebase is a design-debt-driver. Old code has more bugs on average, because standards have moved and review practices have matured. A legacy system with diverse vulnerabilities is behind the times. A legacy system with the same vulnerability over and over is holding a primitive.

Not every misconfigurable product is a design-debt-driver. If operators keep setting things up unsafely, that is usability debt, and the fix is better defaults. The pattern here is internal: the component produces CVEs in its own code, not in how administrators wire it together.

Defender playbook

For each repeat-offender component in your stack, list its CVEs from the last three years side by side. If the descriptions rhyme (parser confusion, auth-gate failure, JMX surface, kernel-mode binary format) you are looking at a design-debt-driver. Budget for another one this year. It is coming.

Invest proportional to the design debt, not to the latest patch severity. A CLFS patch closes one flaw in a stream that has never stopped. Architectural alternatives, when they exist (kernel sandboxing, microkernel decomposition, component replacement), return more safety per dollar than patch velocity ever will.

Factor design-debt into build-vs-buy and renewal decisions. If you are buying a product built on a design-debt-driver, you are buying a subscription to its bug class. Price it accordingly, including the cost of patches, emergency deploys, and the occasional in-wild CVE gap.

Read advisories for the pattern, not the instance. A vendor announcement of "we fixed this specific thing" is often followed six months later by the same fix wording with a new number. The pattern in the advisory stream predicts the next CVE better than any signature.

Sandbox what you cannot rearchitect. You cannot fix someone else's design. You can shrink the blast radius of the next inevitable bug by putting the component in a container, a separate VM, or a capability-restricted mode where the primitive it produces is less useful.

Kinship

Unpatchable Primitive. The escalation shape. Design-debt-driver is patches keep coming AND the class keeps coming. Unpatchable-primitive is patches cannot close the primitive at all. A design-debt-driver that never converges eventually IS an unpatchable primitive.

Disclosure After Exploitation. Design-debt components are frequently first known through in-wild exploitation because the attackers also know the substrate. They find the next instance before the vendor does, and they use it, and the vendor learns about the class from the breach report.

Revocation Gap. The ongoing nature of design-debt-driver means new revocation gaps open on a predictable cadence. Each fresh CVE has its own window of in-wild exploitation before the patch, before the deploy, before the credentials sprayed during exploitation are rotated. The gaps compound across instances.

A design-debt-driver is not a CVE problem. It is a subscription.