Back to Blog
Engineering

Five Regex Bugs I Found Auditing My Own Text Tools

· Sanjeev Pulakurthi

Every tool on this site runs entirely in the browser, and a lot of them lean on regular expressions to do the actual work — split a string, match a pattern, replace a substring. Regex is fast and dependency-free, and for a long time I assumed it was also correct. A recent pass auditing every tool's documentation against its actual behavior said otherwise: five separate tools had a real gap between what the page claimed and what the code did, and every one traced back to regex reaching past what it can reliably do.

None of these were exotic edge cases. They were the kind of input a normal person pastes on a normal day — a comparison operator, a quoted sentence, a plus-addressed email, text typed with Caps Lock stuck on. This is what broke, why, and the fix for each.

The JWT decoder that failed on over 95% of real tokens

A JWT Decoder splits a token on its dots and runs atob() on the header and payload to read the claims — no secret key needed, since only the signature is protected. The bug: JWT segments are base64url encoded (RFC 4648 §5), which substitutes - for + and _ for /, and drops padding. atob() implements plain base64 and throws InvalidCharacterError the moment it hits either substituted character.

The canonical jwt.io example token happens to avoid both characters, which is exactly why it works as a demo — it is short and clean. A realistic payload is not. Each base64 character has roughly a 1-in-32 chance of encoding to + or / before the base64url substitution, so across a 100-character segment the odds that none of them show up are under 5%. Over 95% of real-world tokens should have broken this tool, and only the toy examples worked. The fix was three lines: swap -/_ back to +//, pad to a multiple of 4, then hand it to atob().

The HTML stripper that ate half a sentence

A Strip HTML Tags tool used a single regex, /<[^>]*>?/gm, to delete anything between < and >. The trailing > was optional, which is the part that broke it: paste "Check if x < 10 and y > 5 before continuing." and the regex finds the first <, consumes everything up to the next > it can find — including the entire clause "10 and y" — and deletes all of it. Output: "Check if x 5 before continuing." Half the sentence, gone, with no error and no warning.

This is the textbook argument against parsing HTML with regex, but it is worth stating precisely why: a browser's actual HTML5 parser only opens a tag on < followed by a letter, /, !, or ? — a < followed by a space is just a literal less-than sign. Regex has no such rule; it just matches brackets. The fix was to stop guessing and use the real parser already sitting in every browser: new DOMParser().parseFromString(input, 'text/html').body.textContent. Same one-line simplicity, but now "x < 10" survives untouched and real tags still get stripped — with named entities like &amp; decoded for free as a side effect of using an actual parser instead of a pattern.

Three smaller bugs, same root cause

A Strip Punctuation tool's own documentation claimed it removed quotes and brackets. Its regex did not — the character class simply never included ", ', [, ], or ?. He said "hello, world!" [test] and left? came back with only the comma and exclamation point gone; the code and the documentation had quietly drifted apart.

A Title Case Converter had the opposite problem: it capitalized the first letter of each word but never touched the rest, so HELLO WORLD stayed HELLO WORLD — a title-case tool that only worked on input that did not need it.

An Email Extractor's pattern for the local part of an address did not include +, so alice+newsletter@example.com — an entirely standard plus-addressed email — came back as newsletter@example.com, silently dropping the part that actually routes the message.

What ties these together

Every one of these five bugs shipped with tests that passed, because the tests used clean input. Regex and simple string transforms are correct in the cases you thought to check and silently wrong in the ones you did not — there is no exception thrown, no error path, just quietly wrong output that looks plausible enough to ship. The fix in every case was the same instinct: stop trusting the pattern and verify against real input, including the input nobody imagined — a comparison operator, a quoted sentence, text already in the wrong case, an email address with a plus sign in it. If a tool transforms text, the question worth asking is not whether the regex is correct. It is what a normal person pastes on a normal Tuesday that you have not tried yet.