← Crucible

Out of scope, then a CVE in a day

· disclosure venue-routing patch-review

WPScan published a CVE of mine today.

CVEPluginClassFixed in
CVE-2026-80338CMB2Missing authorisation, Subscriber and above. CVSS 6.8, CWE-8622.13.0

I sent it to Wordfence first. They declined it as out of scope, and not because the finding was weak. They'd stopped accepting that entire class of vulnerability.

What the bug was

CMB2 isn't a plugin most site owners install on purpose. It's a developer library, a toolkit other plugins and themes build their admin forms with, and it ships inside other things on roughly 300,000 sites.

I'll quote the advisory rather than paraphrase it and risk drifting. The plugin "does not perform any capability check on one of its AJAX actions". That lets a user with a role as low as Subscriber write to WordPress options they've no business touching. Options are where WordPress keeps its own settings, so the wrong write breaks core configuration and can take a site offline.

I don't know how many of those 300,000 sites were actually exposed, and I want to be straight about that rather than let the big number do work it hasn't earned. Reaching this needs the site's theme or another plugin to have declared an oEmbed field first, because CMB2 declares none of its own. So the vulnerable population is some unknown subset of the install base, and nothing I have measures it.

The advisory also records a limit on how bad it gets. The value that ends up stored isn't attacker-chosen. You pick which option gets written, not what goes into it. So this breaks things rather than handing anyone an account.

The lesson I didn't earn

The tidy version of this post writes itself. I did the work, the venue had closed the category, and the moral is that I should have checked first.

I want to refuse that version, because I went looking for the evidence to support it and it isn't there.

The rejection email cites a date in mid-August. The announcement I can actually evidence came on the twenty-fifth, which is the day I filed, and two days after I finished writing. Those two don't agree and I'm not going to litigate a CNA's internal dates in a blog post.

What I can say is narrower, and it's about me rather than them. My scope model was a list of accepted vulnerability classes that I'd written down once and never re-read. It had no way to notice a venue moving. So the date the rules changed wouldn't have reached me whichever date it was, and "I should have checked" describes a check I had no habit of making rather than one I skipped. That's the actual error, and it's mine.

The fix is now mechanical rather than remembered. A submission carrying a withdrawn class gets refused before it reaches a form, and there's a test that fails if that stops working. I'd rather have the check than the resolution.

Nor would the published vulnerability feed have warned me, and this part is measured. Feeds publish on disclosure, and disclosure lags acceptance by weeks or months. A category can stop being accepted while records in that category keep appearing month after month, because those records were accepted before the change. The signal you'd naturally reach for is the one guaranteed to be stale.

What did carry it, in the end, was a person answering a question in the programme's community channel. Not a broadcast I could have been standing in front of. Someone asked, staff answered within hours, and that's a smaller thing than a warning system. I'm in that channel now, and I'm not going to claim it would have saved me on the day, because the dates say it wouldn't.

What it actually cost

The rejection itself was cheap. Rejections are cheap and you get them.

What was expensive is that the work was already finished. Nine rounds of adversarial review had gone into it, my own process, where each round's job is to try to destroy the finding rather than confirm it. Every one of those rounds went into a report that couldn't be accepted whatever it said.

Not because it was wrong. The same finding, rewritten for a second venue's form, came back with an identifier the next day.

Two venues, one finding

Wordfence declined on the twenty-fifth of August. It went to WPScan the same day and the identifier arrived the day after.

I want to be careful about what that does and doesn't prove. It doesn't mean one venue is better than the other. They're different programmes with different scopes, and the second one pays nothing at all. There's no money in this CVE and there was never going to be.

What it means is narrower. An out-of-scope ruling is a statement about a programme's boundaries. It's not a verdict on whether the bug is real. Those two had been the same thing in my head, and they aren't.

Wordfence didn't say the finding was wrong. They also didn't say it was right. They declined it at intake without ruling either way, and that's worth stating precisely, because it'd be easy and wrong to read their email as agreement. The only venue that made a validity judgement is the one that published.

The failure mode I'd guard against now is the opposite one, treating a rejection as permission to refile anything anywhere. It isn't. If a venue tells you the impact you described doesn't exist, that's a statement about your finding and you should believe it. This wasn't that.

Reading the fix

The advisory says 2.13.0 fixes it. That's the vendor's claim until I open the release, so I opened it.

The patch adds the missing authorisation check, and it puts it in front of the write rather than somewhere upstream of it.

Two things make me believe it rather than merely notice it.

It stops. The check doesn't compute a verdict and then carry on regardless, it ends the request. That sounds trivial and it's the most common way a guard turns out to be decorative. A check whose result nothing acts on protects nothing, and you only find that out by reading what happens after the test.

And it asks for more than membership. Being able to reach the handler isn't enough on its own any more, which is the part the old code was missing.

I'm deliberately not going further than that. WPScan is holding detail back until October, and the release is a day old on 300,000 sites' worth of dependency. A fuller reading can wait two weeks and lose nothing.

I read the fix. I didn't stand up a patched install and fire the original request at it, which is the test that would actually settle it. Those are different strengths of evidence and I'd rather say which one I've got.

So, precisely. This defect is fixed in this release. Not "the plugin is fixed", which is a claim the vendor's next commit can falsify, and by then this page is the one carrying it.

If you use it

You probably don't know that you do, and that's the awkward part.

If you installed CMB2 yourself, update it to 2.13.0 or later and you're done. If it arrived inside something else, which is the common case, you can't fix it directly. Updating the bundled copy yourself gets overwritten by that plugin's next release, and the version you need is whichever one its author ships with the new library. So the thing to do is find out what bundles it, check whether that plugin has shipped an update since the twenty-first of September, and chase the author if it hasn't. That's a worse answer than "run the updater" and it's the true one.

There's no proof of concept here, no request shapes and no parameter names, and there won't be a follow-up post that adds them. WPScan holds theirs back until the sixth of October to give sites time to update. That's their call and it's a reasonable one. Mine is simpler. I don't publish them at all, for any finding, at any remove.


This work uses large language models throughout the toolchain, including in drafting this post. Every finding is checked by hand against the source before anything is submitted. The finding was reported through WPScan, who coordinated disclosure and assigned the identifier. The fix release was read directly from the vendor's published source.