The Regex That Invented Its Own 127-Character Quote
Thirty-one of thirty-three social posts failed our quoted-phrase check in one run. Two passed. That ratio is the whole story, if you know how to read it, and we did not read it correctly for longer than we should have.
The check exists to catch a specific kind of problem: a post that puts words in quotation marks and attributes them to a source article that never said them. Fabricated quotes are the sort of thing that looks fine at a glance and is genuinely bad if it ships. So every post that quotes anything gets scanned before it goes anywhere near a schedule.
On this run, the scanner rejected almost everything we had written. Our first reaction was to start rewriting posts. Our second, slower reaction was to notice that thirty-one rejections sharing one reason code is not what bad content looks like.
What a broken checker looks like from the outside
Bad content fails for different reasons. One post has a wrong number, another has a dangling sentence, a third genuinely does misattribute a quote. A batch of real defects is messy — it fails on a scatter of causes, in proportion to how many different ways a batch of writing can go wrong.
A batch that fails on exactly one reason, at a rate that high, is not describing the content. It is describing the check.
We had seen this shape before, in a different system, on a different day: a security scan that turned out to be reacting to a registry outage rather than to any vulnerability in our code. Same signature — one cause, applied everywhere, dressed up as findings — and the same lesson, which took us longer to apply here than it should have given we had already written it down once.
The mechanism
The checker looks for quoted phrases with a regular expression built around a negated character class: [^"]{8,}. Read plainly, that pattern means “eight or more characters that are not a double quote,” sitting between two quote marks. It is meant to capture the text of one quotation.
The bug is in what a negated character class does with a newline. A full stop in a regular expression, by default, does not match a line break — that is a widely known and deliberately chosen limitation. A negated character class has no such limitation. [^"] matches any character that is not literally a double quote, and a newline is not a double quote, so it matches straight through it. The class does not stop at the end of a line. It does not know lines exist.
Our house style for these posts is a bullet list, several lines long, and it was common for two separate bullets to each contain a short quoted term — a product name, a stat label, a phrase we wanted to set off visually. Two bullets, two pairs of quotation marks, several lines apart.
The regex found the opening quote of the first term. It kept consuming characters — spaces, bullet markers, line breaks, the text of the bullets in between — until it hit the next quote character anywhere later in the post. That next quote character was very often the closing quote of an unrelated term two bullets down.
The result was a “quotation” stitched from the tail of one bullet, the whole of the next, and the head of the one after that: 127 characters long, grammatically nonsensical, and attributed by the checker’s logic to a source article that had never produced anything resembling it. The checker was not wrong that the string it built did not appear in the source. It was wrong about there being a string to check in the first place.
Any post with two quoted terms on separate lines was structurally guaranteed to trip this. That was not a rare shape. It was the house style.
Why we did not see it faster
The honest answer is that the failure looked exactly like the thing the check was designed to catch. A tool reporting “quote not found in source” is doing its job when it says that about a real fabrication. It is very hard to distinguish, from the report alone, between “this quote is fake” and “this quote does not exist and the checker built its own.”
What broke the stall was the ratio, not the text. Ninety-four percent of a batch failing on identical grounds is a statement about the batch’s uniformity of structure, and real writing is never that uniform in its defects. Once we asked what property nearly every post shared — rather than what each post individually said — the shared property was obvious: multiple quoted terms, multiple lines apart, house style throughout.
The fix, and proving it both ways
The fix is one character. The negated class becomes [^"\n]{8,} — everything that is not a quote mark, and explicitly, also not a newline. The class now stops at the end of a line the way a person reading the bullet list would.
A one-character fix earns almost no trust on its own, so we verified it in both directions rather than one. We took a post we knew contained a genuinely fabricated quote and confirmed the corrected regex still caught it — the fix had to close the false positive without opening a false negative. Then we reverted the regex and confirmed the real, legitimate post went back to failing, which told us the original bug was actually the cause of the original failures and not a coincidence sitting next to some other problem.
Both directions had to hold. A fix that only stops the false positives, without a check that it still catches real fabrications, is a guess with a passing test. A fix that isn’t reverted and reproduced isn’t confirmed at all — it’s a plausible story.
What this is worth to a business that is not us
Almost nobody running a small business writes their own quote-fabrication detector. The pattern generalises past that specific check.
A gate that blocks almost everything on one identical reason is telling you about itself. Real defects are heterogeneous. When thirty-one of thirty-three fail the same way, stop asking what is wrong with the thirty-one and start asking what the checker assumes about all thirty-one that happens to be false.
Test a fix in both directions before you trust it. It is not enough that the false positives stop. Confirm the check still catches the real problem it exists for, on a case you know is real, and confirm reverting the fix reproduces the original failure. One direction proves the symptom went away. Both directions prove you found the cause.
A regex that “obviously” only matches what you intend often does not. A negated character class crossing a line boundary is a one-line difference from correct, and it will pass every test you did not think to write across multiple lines.
If your business runs any kind of automated review — a content approval step, a compliance check, a build gate — the question worth asking whoever built it is not whether it currently passes. It is what happens when it blocks something correct, and whether anyone would notice the ratio before they started rewriting content that never needed it. We help clients build exactly that kind of resilience into their systems through managed IT support, and it is the same question we ask about our own checks first.
We have written up two companion incidents from the same family of mistake: a registry outage misread as vulnerability, and gates that reported success doing nothing. Between the three, the shared lesson is that a gate’s own failure mode is rarely reported as “the gate is broken” — it reads as a finding, until the pattern across the batch says otherwise. We also went looking for the broader discipline behind catching this kind of thing before it ships, which is the subject of an adversarial review pass we now run on work before it merges.
Ash Ganda is the founder of Cloud Geeks, which provides managed IT support and cyber security for Australian small and medium businesses.