Wider than anything I sent
A CVE of mine went public on 20 October.
| CVE | Software | Class | Fixed in |
|---|---|---|---|
| CVE-2026-100142 | FluentCart | Unauthenticated customer address disclosure, by insecure direct object reference. CWE-639. CVSS 5.3 | 2.2.0 |
If you run FluentCart, update to 2.2.0 or later.
I sent the coordinating body four corrections to this record's affected range, over seventeen days. Then I offered to stop, and they took me up on it. When they published, their range was wider than anything I'd sent them.
WPScan is holding the proof of concept on this record until 3 November, to give shops time to update. I don't publish proofs of concept at all, for any finding, so that date changes nothing about what I'd write. What it does bound is how much of the mechanism I describe, and that bound is tighter than people expect: their advisory is the ceiling, and anything my report knows that their page doesn't say stays unsaid until the hold lifts. That includes some things I'd normally find interesting.
What the record says
FluentCart is an e-commerce plugin for WordPress, on about 8,000 shops when I filed. WPScan coordinated the disclosure. Their description is the furthest I'll go into how this works:
The plugin does not verify that a stored customer address selected during checkout belongs to the requester, allowing unauthenticated attackers to attach any customer's stored address to an order they place and read it back, disclosing customers' postal addresses, phone numbers and company details.
— WPScan advisory for CVE-2026-100142, published 20 October 2026.
A shop's customers can save an address for next time. Somebody placing an order could pick an address belonging to a different customer, then read it back off their own order. No account needed to do it.
Nine separate fields belonging to the other customer came back: six parts of a postal address, a phone number, a company name and a company registration number. Two things didn't come back, and they matter to anyone judging how bad this is — not the victim's name, and not their email address. Both of those negatives were predicted before I measured them, by two independent reviews whose only instruction was to break the report. Those reviews are language models, not people, and "two independent reviews" reads like two humans when it wasn't.
This earned an identifier and no money, and the reason is worth stating plainly because it isn't about this bug's merit. There are two databases I can report to. One of them pays bounties; the other doesn't, and is the one that published this. Wordfence is the one that pays, and in August it stopped accepting this whole class of report — insecure direct object reference now gets rejected at intake, whatever the finding looks like. I filed this in September, after that. And the shop count rules out the fallback: their threshold for anything outside their priority classes is 50,000 installs, against about 8,000 here. I found it on 19 September, filed it on 21 September, and it published 29 days later.
The control that nearly didn't happen
My early runs read that data from a client which was, incidentally, carrying a live session. I hadn't asked for it and the finding doesn't need it. But one client can't tell "this works anonymously" from "I happened to be signed in" — those two produce identical output. And my report says the fields were read with no session of any kind. Unproven, that sentence is exactly the shape of claim that gets a report refused on accuracy rather than on severity, which is the expensive way to lose.
So the deciding run used two isolated cookie jars, and the reading one had to prove it held nothing first. I pointed it at the site's own administration area and watched WordPress bounce it to the login page, holding zero cookies. Then it read the nine fields anyway. Nineteen checks out of nineteen passed, with the raw response written to disk, so the field list is re-derivable rather than taken on my word. The session was incidental to the disclosure, not a precondition of it. That's a positive claim now. Before, it was an assumption I hadn't noticed making.
Four corrections in seventeen days
My report named no fixed version, because there wasn't one. An affected range with no upper bound reopens every time the vendor ships, and this vendor ships often. So on each release I pulled the files my report cites, at the old tag and the new one, pinned to real release tags rather than to whatever trunk happened to hold that day. Then I compared them. Each time the finding was still there, and each time I wrote a letter moving the ceiling up by one release. The ceilings went 1.6.5, then 1.6.6, then 1.7.0, then 1.7.1.
Three of the four were applied: each time, the range the public record carried moved to the version I had measured. Not one of the four letters was refused.
A coordinating body re-deriving a measurement on its own instrument and arriving where you did is the strongest outside check this work gets.
Then I offered to stop, and they went past me
By the fourth letter I'd started to wonder whether I was adding noise to somebody's queue. So I asked, in the letter itself — I said to tell me if they'd rather I stopped and simply let them re-check at disclosure, and that I was happy either way. I want to be accurate about who raised it: they didn't tell me to stop out of the blue. I offered, and they accepted.
The reply did two things at once. The range on the record was already wider than the one I'd just asked for. And it left me a better rule than the one I'd been working to.
The rule is keyed on behaviour rather than on a version number: not "send a note on every release" but "send one if a release changes what the code does". That's the part I actually adopted.
I kept checking and sent nothing
The instruction was to stop sending. It wasn't to stop looking, and I didn't read it that way. Three more releases shipped and the finding survived all three: 1.9.0, then 2.0.0, then 2.1.0. On the last of those, 45 of 45 fetches came back pinned to a real release tag with no trunk fallback, which matters because a tag that's been pruned silently falls back to trunk and then two different versions can hash identical for no reason at all.
I transmitted none of it. Their published ceiling was 1.8.0 by then, and I could measure that it was three releases short. That was accurate and useful to know. It also wasn't mine to send, by their own written request, and the risk of it being wrong was theirs to carry. Then they published, with the range given as below 2.2.0.
Every ceiling I ever transmitted was too low. The last by four releases. The first, at filing, by eight.
What the four letters were worth
Here's the honest accounting, and it doesn't resolve neatly. Every range I sent was right: each release I called vulnerable sits inside the range they published, and twice they said in writing that their own comparison reached my result. So this isn't a story about a venue correcting my work — we never disagreed about a range.
I'm deliberately not claiming more than that, because the working notes behind those letters carry four corrections to my own measurements, and one of them I withdrew completely once I found the tool that produced it had over-captured by a factor of seven. The ranges held. The workings needed fixing more than once, and nobody outside caught those — I did, later, which is the only reason I can tell you about them.
What was wrong was my premise. I treated the range as an obligation that reopened on every release. The party carrying it was already re-checking on its own cadence and settling the range at disclosure, in one action, at the end. Four letters moved a number that was going to be overtaken anyway.
The change I made is narrow, because the failure was narrow. The release-by-release check still runs, since it's what tells me whether my own finding still holds, and that was never the part anybody objected to. What changed is transmission, which now waits for the disclosure event. One exception survives: a vendor shipping a fix the coordinating body hasn't recorded, while the report is still open, closes a ticket as already patched and costs the identifier. That one I still send.
What I can't settle is whether the fourth letter was worth sending. It was neither accepted nor refused — it was overtaken. Nothing in the record separates "that ask would have been granted" from "it was never read", and no measurement available to me would.
Reading the fix, and reading the page
A "fixed in" column is a claim about work I didn't watch being done. So before writing that number down I read the release itself, against the one before it, pinned to real tags. The defect in this record is closed in 2.2.0. That's the claim I'm making, and it's the only one I'm entitled to.
I'm not going to say where in the code the change landed, or which files moved and which didn't. The advisory doesn't say, and with the proof of concept held until 3 November, that detail is theirs to release and not mine. There's something I'd like to say about what reading it taught me, and it will keep until the hold lifts.
And one limit on all of it. I read source, and I didn't fire the original request at the patched release to watch it fail. Reading tells you a guard is present and reachable. Firing it tells you about the cases nobody thought to write a guard for. So "I found nothing else wrong with 2.2.0" isn't a sentence I've earned.
One more thing about that page, for anyone who reads advisories by machine. The sentence announcing the 3 November hold doesn't live in the page's text. It sits inside a form field, and a plain text read drops it. So a naive fetch of that URL reports no proof-of-concept section at all, which looks like nothing withheld, and is confidently wrong in the publishing direction.
What I'd take from it
Ask who's measuring, before you ask how often to write. Those are separate questions and I'd merged them into one habit.
I wrote a post a month ago about the other public record on this plugin. There I compared the wrong pair of releases and talked myself into a correction nobody was owed. This time the ranges I sent were all right, and all still too low. Being right and being useful turn out to be different axes, and I'd been scoring myself on the wrong one.
The party who took me up on stopping had the better model of the problem. They were measuring the same thing I was, on a cadence that made my letters furniture.
This work uses large language models throughout the toolchain, including the adversarial review described above and the drafting of this post. Every finding is checked by hand against the source before anything is submitted. The finding was reported through WPScan, who coordinated with the vendor, assigned the identifier, scored it and published the record. The fix release was read directly from the vendor's published source, pinned to real release tags, with no end-to-end re-run of the original request.