A patch diff with a second vulnerability slipping through the gap the fix left open

The Fix of the Fix: public CVEs that patched the PoC, not the bug

Five recent, fully-public CVEs where the patch closed the reported proof-of-concept and left the sink wide open — grouped by the shape of the mistake, so you can grep for it.

In my last post I argued that the highest-yield question in vulnerability research is "did the fix guard the sink, or just the instance?" Abstract. So here are five public, already-disclosed CVEs from 2026 where the answer was "just the instance" — grouped by the shape of the mistake, because the shapes repeat and you can grep for them.

Everything below is sourced from public advisories. No embargoed work here.

Shape 1 — Enumerate one address, not the range

axios CVE-2026-42043 (GHSA-pmwg-cvhr-8vh7) is a textbook fix of the fix: it's the incomplete patch of CVE-2025-62718. The NO_PROXY loopback guard in normalizeNoProxyHost() string-compared against the one canonical address 127.0.0.1 — so 127.0.0.2, 127.1.1.1, anything else in 127.0.0.0/8, sailed past the proxy bypass and reached localhost services. SSRF. Fixed in 1.15.1.

Fedify CVE-2026-50131 is the same shape one layer up. Its SSRF denylist blocked the obvious privates (10/8, 127/8, 169.254/16, 172.16/12, 192.168/16) and treated everything else as public — including special-use, reserved, multicast, benchmarking, and carrier-grade NAT (100.64.0.0/10), which fronts cloud-metadata proxies. CVSS 8.6.

A guard that enumerates the bad values will always miss one. The complete fix is the inverse: allowlist the good class (global-unicast) and deny the rest. Grep for loopback/range checks that == a single literal instead of testing the CIDR.

Shape 2 — Guard the root object, not the nested one

axios CVE-2026-44489 bypassed axios's own prototype-pollution fix (GHSA-q8qp-cvcw-x6jj). That fix wrapped the top-level config with Object.create(null) — but nested objects rebuilt by utils.merge() (like config.proxy) were still plain {} carrying Object.prototype, and setProxy() read proxy.username/password with no hasOwnProperty guard. Pollute Object.prototype.username and you inject a Proxy-Authorization header on every proxied request. Fixed 1.16.0.

Null-proto protection at the entry object is incomplete the moment a deep-merge or clone re-creates children as plain objects and a sink reads attacker-controllable keys off them. Audit both halves: where the root is hardened, and where merges emit fresh {}.

Shape 3 — Escape the right injection, the wrong context

samlify CVE-2026-46490 (GHSA-34r5-q4jw-r36m): template substitution escaped values placed into attribute context but not values dropped into element text (<saml:AttributeValue>…</saml:AttributeValue>), so an ordinary user could inject XML markup into element content. The escaper was correct — for one of the two contexts it fed.

When a fix adds escaping, the question isn't "did they escape?" — it's "which context?" Attribute vs element-text vs comment vs CDATA. Find a sink that emits into the context the escaper doesn't cover. (samlify's 2.13.0 fix did the right thing and escapes both.)

Shape 4 — Validate one location, act on another

Python tarfile CVE-2026-11940 defeated the filter='data'/'tar' extraction hardening with a crafted archive: a hardlink that references a symlink stored at a deeper name than the hardlink itself. The extraction fallback validated the symlink at its archived location but recreated it at a different on-disk path — escaping the destination directory. It's part of an active family (CVE-2025-4517, CVE-2026-7774; cpython#135034), and because it's stdlib it cascades into every wrapper that calls extractall.

Time-of-check vs time-of-use, wearing an archive costume: the link is safe relative to where the archive says it lives, then materialized somewhere else. Audit any extractor that runs realpath/safe_join on the link's stored name rather than its final write path.

Shape 5 — Let untrusted input flip the trust switch

Eclipse Theia CVE-2026-44691 (GHSA-g9jw-92q7-g7fj): custom task definitions in .theia/tasks.json / .vscode/tasks.json executed without requiring workspace trust — open a malicious cloned repo, get arbitrary commands. The sharp edge: a workspace-supplied .theia/settings.json could disable the AI-chat tool-confirmation prompt, turning "send one chat message" into zero-click RCE. Fixed 1.69.0.

The workspace-trust vein isn't only "which loaders gate trust." It's also whether untrusted, workspace-controlled config can toggle the trust/confirmation settings themselves. A trust gate you can switch off from inside the untrusted boundary isn't a gate.

Two from my own cycle (now public)

Skin in the game — two incomplete-fix findings I reported this cycle, both now published:

  • WWBN/AVideo — CVE-2026-55173 (GHSA-wc3f-xc32-435f). Denylist misses a sibling. The patch for CVE-2026-33482 hardened sanitizeFFmpegCommand by denylisting $(), {}, && and newlines — but left other shell separators reachable, so command injection survived the "fix."
  • eventschedule — CVE-2026-55190 (GHSA-664j-55hw-p8fm). Filter never normalizes. The isUrlSafe SSRF guard is defeated by a bracketed IPv6 literal: parse_url keeps the [], so the host the guard inspects never matches its IPv6 checks and the request reaches internal addresses.

Both are the exact shapes above, found in the wild. Four more from the same cycle are still in coordinated disclosure — they stay unnamed here until their advisories publish. Same rule as last time: share the map, hold the loaded ones.

The throughline

Four of these five are SSRF, prototype pollution, or path traversal — the veins with one sink and many roads. A patch that walks down one road and bricks it leaves the others open. The shapes — enumerate-one-not-the-range, guard-root-not-nested, escape-wrong-context, validate-here-act-there, flip-the-trust-switch — are reusable grep targets. Learn the shape once; find it across the ecosystem.

And the discipline from last time still applies: a residual that looks reachable isn't a finding until you've proven the payload hits the sink. Read the fix commit, not the advisory.

Sources: NVD / GitHub Security Advisories / GitLab advisory DB for each CVE above.

No comments yet