Zerowidth CleanerAll measured guides

CLAUDE WATERMARK REMOVER · PRACTICAL TEST

Does URLSearchParams reject malformed percent text like decodeURIComponent does?

Our %FF control throws URIError with decodeURIComponent, but URLSearchParams returns U+FFFD and serializes it as %EF%BF%BD. The incomplete %4 is retained as literal text. A successful form parse does not establish valid original UTF-8.

Tested October 18, 2026 · v24.19.0 · 4 measured runs

Open the Claude text cleaner · Full measured data

Code-point comparison for Interrupted byte

Measured inputs and outputs

These are locally constructed test strings, not evidence that Claude inserts these characters. We executed the saved homepage script snapshot with a minimal DOM harness and compared exact strings. The invisible-checkbox state and dash mode appear in each row. The observations below test this page's specific question. Destination observations run independently on the exact input and cleaned strings in the recorded Node runtime. We make no detector-score or statistical-watermark removal claim.

Before and after observations; full code points and strings are in the JSON download.
Fixture and modeInput string and code pointsOutput and cleaner statusObserved before / after
Interrupted byte
direct string / keep / invisible true
"%4​1"
U+0025 U+0034 U+200B U+0031
"%41"
Removed 1 invisible character
{"strict":{"errorName":"URIError"},"formValue":"%4​1","serialized":"q=%254%E2%80%8B1","valueCodepoints":["25","34","200b","31"]}
{"strict":{"value":"A"},"formValue":"A","serialized":"q=A","valueCodepoints":["41"]}
Incomplete escape
direct string / keep / invisible true
"%4"
U+0025 U+0034
"%4"
No selected invisible characters found
{"strict":{"errorName":"URIError"},"formValue":"%4","serialized":"q=%254","valueCodepoints":["25","34"]}
{"strict":{"errorName":"URIError"},"formValue":"%4","serialized":"q=%254","valueCodepoints":["25","34"]}
Invalid UTF8 byte
direct string / keep / invisible true
"%FF"
U+0025 U+0046 U+0046
"%FF"
No selected invisible characters found
{"strict":{"errorName":"URIError"},"formValue":"�","serialized":"q=%EF%BF%BD","valueCodepoints":["fffd"]}
{"strict":{"errorName":"URIError"},"formValue":"�","serialized":"q=%EF%BF%BD","valueCodepoints":["fffd"]}
Valid ASCII escape
direct string / keep / invisible true
"%41"
U+0025 U+0034 U+0031
"%41"
No selected invisible characters found
{"strict":{"value":"A"},"formValue":"A","serialized":"q=A","valueCodepoints":["41"]}
{"strict":{"value":"A"},"formValue":"A","serialized":"q=A","valueCodepoints":["41"]}

The same byte spelling produces an exception or replacement

We compare a strict component decoder with a q value parsed through URLSearchParams. For the %FF control, decodeURIComponent throws URIError, while the form parser returns a single replacement character, U+FFFD. Serializing that parameter object produces q=%EF%BF%BD. The saved code-point list identifies the replacement independently of how the browser renders it. Cleanup does not change the original ASCII percent spelling, so both branches repeat these results. Our earlier strict-decoder page records the exception but does not measure this form parser. The new matrix answers the receiving parser question and demonstrates a concrete representation change that a no-exception test would otherwise conceal.

Incomplete notation and interrupted notation follow other paths

For %4, the strict decoder throws, but the form value remains literal percent-four and its serialized form escapes the percent sign. The interrupted %4 plus U+200B plus 1 initially remains literal interrupted text through form parsing. Deleting the actual marker joins a valid %41 escape, and both decoders then produce A. The unmarked %41 control produces A throughout. Recording the form value, its code points and the serialized parameter string distinguishes accepted literal notation from decoded bytes and replacement output. The observer prefixes a fixed q= to tiny component strings; it does not parse arbitrary URLs, handle duplicate keys or test a server’s decoding library. No recursive decoding pass is performed.

Acceptance and preservation need separate requirements

If a receiving workflow must reject malformed encoded text, do not infer that requirement was satisfied just because URLSearchParams returned a string. Preserve the raw component alongside the decoded value and apply the documented validation policy at the correct stage. A replacement character does not reveal the original byte that failed, and resaving the decoded string cannot reconstruct that history. Conversely, retained percent text is not proof of successful percent decoding. The four fixtures give controls for rejection, literal retention, replacement and ordinary decoding. They establish these local operations only, not a universal URL sanitizer, an observed Claude export defect or a method for removing statistical watermarks. Use the downloadable script to test your actual parser boundary separately.

Reproduce this test

Save reproduce.cjs and tested-app.js in the same folder. Run the command below with Node.js. The harness prints its runtime, script SHA-256 and every measured row. Compare those rows with the original record. Using a newer script or runtime creates a new experiment; retain the version information with your rerun.

node reproduce.cjs

Reference and next check

WHATWG form parser provides the relevant primary definition. The table and fixture analysis are original measurements. For broader inspection, use our Unicode inspector. Read the scope distinction before interpreting cleanup as a watermark result.