← All publications
Threat Report Methodology

The Silent Patch Problem

When a security fix ships without details, the diff is the vulnerability. A worked example on the WordPress-core "WP2Shell" unauthenticated RCE.

July 2026 6 pages 9 min read
By codelake Research

Open-source security fixes routinely ship silently: a "maintenance release," a vague changelog, and technical details embargoed for days so defenders can patch before attackers reverse-engineer the fix. But the fix is public in the bytes long before it is public in words — the diff between the vulnerable and fixed version is the vulnerability. On 2026-07-19 WordPress shipped 6.9.5 and 7.0.2, quiet releases that fixed a critical, unauthenticated RCE in core (WP2Shell). Given both versions, a generic predecessor-version differ flagged the fix on both affected branches as a tiny, security-concentrated change — six files, both of the security-relevant ones in the REST server — from the raw bytes, with no CVE detail and no exploit.

The diff between the vulnerable version and the fixed version is the vulnerability. Attackers do this routinely. Defenders mostly do not — because they lack the two artifacts side by side, and a way to tell a security-relevant change from ordinary churn.

The numbers

0
findings from source-level SAST on WordPress core — WP2Shell is a control-flow bug, not a taint sink
codelake pipeline
6
files changed in the 7.0.2 fix — both security-relevant ones inside wp-includes/rest-api/
WP 7.0.1→7.0.2 diff
2
branches carry the same fix (6.9.5 and 7.0.2) — the differ flagged both
codelake versiondiff
0.40
security concentration of the 6.9.4→6.9.5 diff vs ~0.07 for the 6.9→7.0 major release
codelake versiondiff
9
ecosystems the predecessor differ now runs across, continuously
codelake, Jul 2026
1
detector, two signals — silent security patch AND injected-poison update
codelake versiondiff

Inside this report

The WP2Shell vulnerability was discovered and responsibly disclosed by Searchlight Cyber, who withheld technical details to give sites time to patch. codelake did not discover WP2Shell.

What this report demonstrates is a detection method: that a generic predecessor-version diff reconstructs the location and shape of a silently-shipped security fix from the public patch bytes alone — turning every "quiet" security release into an actionable n-day signal and machine-learning training data, without waiting for the write-up. No working exploit or reproduction is published.

WordPress core exposes a REST API and, since 5.6, a batch endpoint (/wp-json/batch/v1/) that lets one HTTP request carry several sub-requests. WP2Shell is a pre-authentication RCE reachable through that batch machinery; the discoverers recommend blocking the batch/v1 route at the WAF as a stop-gap.

The upstream fix touches exactly two files, and the same change lands on both maintained branches (6.9.4→6.9.5 and 7.0.1→7.0.2). class-wp-rest-server.php gains a re-entrancy guard — the top-level handler now refuses to start a fresh REST cycle while a dispatch is already in flight — plus a fix to an array-alignment bug in the batch loop so a failed sub-request no longer knocks the request/response bookkeeping out of step. rest-api.php gains the same short-circuit before the handler's terminal die().

In plain terms: the batch endpoint could trigger a nested top-level REST cycle while another was running. The nested cycle re-initialised global request state — including the current-user context — and the mis-aligned bookkeeping let a sub-request match the wrong handler. The result is a request-confusion primitive: an unauthenticated request executing in a context it should never have reached. None of that was in the release notes. All of it is legible in the diff.

We hold every WordPress-core release we ingest and run the same pipeline on it as on any package: Semgrep breadth rules plus sanitizer-aware source→sink taint analysis. On WP2Shell — and on older known-vulnerable core versions we keep as ground truth — that pipeline reports zero findings.

This is not a bug in the rules; it is a category error. Source-level SAST excels at data-flow bugs: untrusted input reaching a dangerous sink (SQL, eval, command exec, XSS). WP2Shell is a control-flow bug — a missing guard against re-entering a function while it is already running, plus an off-by-one in array bookkeeping. There is no tainted value flowing to a sink.

The honest conclusion: for silently-patched logic and auth-flow flaws, you will not signature your way to the bug. But you do not need to. You need the two versions and a diff.

Before we could diff WP2Shell we hit a more basic problem: we didn't have the vulnerable version. codelake keeps every artifact it scans — we prune nothing. But "we keep everything" is not the same as "we fetched everything." Our WordPress-core ingest pulled only the highest patch of each branch plus a few CVE milestones — always the newest, i.e. the fixed, release. We had 6.9.5 and 7.0.2. We never downloaded 6.9.4 or 7.0.1.

This is the quiet failure mode of security-data collection: by keeping only the current version, you implicitly assume every bug has already been found — precisely the assumption a silent-patch detector must not make. The fix was to ingest the full per-branch patch sequence so each version's predecessor is on disk and diffable. The vulnerable predecessors, including 7.0.1 and 6.9.4, are now in the corpus.

The lesson generalises. Registries are immutable per-version, but what you retain is a choice; browser and IDE extension stores serve only the current version, so version history there must be captured forward, in real time, or it is gone.

The detector is deliberately simple and ecosystem-agnostic. For every package, order its versions and, for each consecutive pair, extract both and diff the file trees. The naive score — "how many security-sensitive files changed" — is useless alone, because a major release changes everything. A 6.9→7.0 jump modifies ~1,300 files; of course some are in rest-api/.

The signal that marks a silent security patch is concentration: a small diff whose changes land disproportionately in security-sensitive code (auth, REST routing, capabilities, SQL, upload, deserialization, crypto). WP2Shell is the textbook shape — 5–6 files changed, and both changed security files are the REST server. The 6.9.4→6.9.5 pair scores 0.40; 7.0.1→7.0.2 scores 0.33. The 6.9→7.0 major release, 95 sensitive files but out of ~1,300, scores ~0.07. The tiny concentrated diff ranks at the top.

Crucially, this is detail-independent. The differ never needs to understand why the batch controller changed. It only observes that a security release changed a small, sensitive slice of the codebase — and hands a human the exact two files to read. That is the entire distance between a "quiet maintenance release" and a reproducible n-day.

A predecessor diff is symmetric. A silent security patch is behaviour removed or guarded between versions; a supply-chain poison is behaviour injected between versions — publish a clean version to earn trust, then ship a malicious update. The same consecutive-version diff scores both: security-sensitive changes on one axis, injected behaviour (a new install hook, child_process/exec, a reverse shell, a raw-IP or cloud-metadata callback, obfuscation) on the other.

We validated the poison side on a live npm case this month, date-format-utils-xz. Its 1.0.3→1.0.4 diff is two modified files, flagged at the top of the poison list: 1.0.4 had modified its install hook to add a reverse shell. Because the poison was slipped into an existing file rather than a new one, a "new-file-only" heuristic would have missed it; the concentration-over-the-whole-diff approach caught it.

One detector, two of the most valuable signals in supply-chain security: this security fix hides a vulnerability, and this update hides a backdoor — both read straight from the bytes.

Silent security releases are an intelligence source, not just a patch. Every quiet "security hardening" release from a widely-used project is a labelled pointer to a vulnerability; with both versions on disk, a defender can localise the fix in minutes and prioritise the exact affected component — often before the public write-up exists.

Retention policy is a detection decision. Keeping only the current version silently assumes the bug is already known. Catching silent patches requires holding the vulnerable predecessors — a retention choice for immutable registries, a now-or-never capture for extension stores.

Match the analysis to the bug class: data-flow bugs → SAST/taint; control-flow and auth-logic bugs → diff the fix. Neither replaces the other. And the by-product is training data — every localised silent patch is a verified vulnerable→fixed pair, every caught poison a clean→malicious pair, exactly the labelled corpus needed to do this triage at scale.

Data appendix · key indicators

IndicatorValueAs ofPrimary source
6.9.4 → 6.9.5 (WP2Shell fix) 5 files · concentration 0.40 Jul 2026 codelake versiondiff
7.0.1 → 7.0.2 (WP2Shell fix) 6 files · concentration 0.33 Jul 2026 codelake versiondiff
6.9 → 7.0 (major, for contrast) ~1,300 files · concentration ~0.07 Jul 2026 codelake versiondiff
Fix location wp-includes/rest-api/ (2 files) Jul 2026 WP 6.9.5 / 7.0.2 release

Registry counts overlap across indices and must not be summed. Survey figures reflect the cited sample, not the global population.

Selected sources

WordPress 6.9.5 and 7.0.2 release diffs (public). WP2Shell discovery and disclosure: Searchlight Cyber (technical details withheld at time of writing). Silent-patch localisation and concentration scoring: codelake predecessor-version-diff analysis.

Keep reading

More from codelake Research