CLAUDE WATERMARK REMOVER · PRACTICAL TEST
Why can searchParams.sort change a URL that has only one query parameter?
Our one-parameter URL changes q=a%20b~ to q=a+b%7E after searchParams.sort. The decoded value remains a b~. This is query reserialization rather than key reordering, and it occurs even when cleanup removes nothing.
Open the Claude text cleaner · Full measured data
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.
| Fixture and mode | Input string and code points | Output and cleaner status | Observed before / after |
|---|---|---|---|
| Marked query direct string / keep / invisible true | "https://example.invalid/?q=a%20b~"U+0068 U+0074 U+0074 U+0070 U+0073 U+003A U+002F U+002F U+0065 U+0078 U+0061 U+006D U+0070 U+006C U+0065 U+002E U+0069 U+006E U+00 … | "https://example.invalid/?q=a%20b~"Removed 1 invisible character | {"before":"https://example.invalid/?q=a%20b~%E2%80%8B","after":"https://example.invalid/?q=a+b%7E%E2%80%8B","valueBefore":"a b~","valueAfter":"a b~","sameHref":false}{"before":"https://example.invalid/?q=a%20b~","after":"https://example.invalid/?q=a+b%7E","valueBefore":"a b~","valueAfter":"a b~","sameHref":false} |
| Serialized reference direct string / keep / invisible true | "https://example.invalid/?q=a%20b~"U+0068 U+0074 U+0074 U+0070 U+0073 U+003A U+002F U+002F U+0065 U+0078 U+0061 U+006D U+0070 U+006C U+0065 U+002E U+0069 U+006E U+00 … | "https://example.invalid/?q=a%20b~"No selected invisible characters found | {"before":"https://example.invalid/?q=a%20b~","after":"https://example.invalid/?q=a+b%7E","valueBefore":"a b~","valueAfter":"a b~","sameHref":false}{"before":"https://example.invalid/?q=a%20b~","after":"https://example.invalid/?q=a+b%7E","valueBefore":"a b~","valueAfter":"a b~","sameHref":false} |
| Form reference direct string / keep / invisible true | "https://example.invalid/?q=a+b%7E"U+0068 U+0074 U+0074 U+0070 U+0073 U+003A U+002F U+002F U+0065 U+0078 U+0061 U+006D U+0070 U+006C U+0065 U+002E U+0069 U+006E U+00 … | "https://example.invalid/?q=a+b%7E"No selected invisible characters found | {"before":"https://example.invalid/?q=a+b%7E","after":"https://example.invalid/?q=a+b%7E","valueBefore":"a b~","valueAfter":"a b~","sameHref":true}{"before":"https://example.invalid/?q=a+b%7E","after":"https://example.invalid/?q=a+b%7E","valueBefore":"a b~","valueAfter":"a b~","sameHref":true} |
| Plain control direct string / keep / invisible true | "https://example.invalid/?q=AB"U+0068 U+0074 U+0074 U+0070 U+0073 U+003A U+002F U+002F U+0065 U+0078 U+0061 U+006D U+0070 U+006C U+0065 U+002E U+0069 U+006E U+00 … | "https://example.invalid/?q=AB"No selected invisible characters found | {"before":"https://example.invalid/?q=AB","after":"https://example.invalid/?q=AB","valueBefore":"AB","valueAfter":"AB","sameHref":true}{"before":"https://example.invalid/?q=AB","after":"https://example.invalid/?q=AB","valueBefore":"AB","valueAfter":"AB","sameHref":true} |
One parameter isolates serialization from order changes
The reference URL contains exactly one query pair, so there is no second key for sort to move. Its initial href uses percent-20 for the space and a literal tilde. After calling the attached searchParams.sort method, href uses a plus for the space and percent-7E for the tilde. The decoded q value is identical before and after that operation. We save both serialized addresses and both decoded values, rather than treating any changed URL spelling as proof that cleanup altered the data. The ordinary AB query provides a stable control: neither sort nor cleanup changes its address. No request is sent to the reserved example.invalid hostname.
The marked fixture contains two separate transformations
Our marked URL additionally ends the query value with U+200B. Parsing the raw URL encodes that marker into the address while the decoded query value still contains it. Sorting changes the space and tilde spelling while preserving the decoded marker. In the independently cleaned branch, the homepage removes the literal marker before URL construction; sorting still changes the remaining space and tilde spelling. The form-style control already uses plus and percent-7E, so its href remains stable. Earlier pages study sorting multiple keys and appending already encoded text. Here the value is retained and a one-key mutation exposes the serialization boundary, without any duplicate-key or remote-server behavior being tested.
Compare the representation your application actually requires
When debugging changed links, compare decoded pairs for a value-level question and exact serialized bytes for an address-spelling requirement. Those are different checks, and a stable decoded value does not guarantee that a cache, signature system or remote service treats two spellings alike. We test none of those consumers here. Preserve the original href when its exact representation matters, and avoid mutating an attached parameter object merely to inspect it. Use the downloadable before-and-after strings to reproduce this narrow result in the recorded runtime. Neither normalization of query spelling nor deletion of a selected invisible character identifies an author or verifies removal of a statistical watermark.
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.cjsReference and next check
WHATWG URLSearchParams serialization 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.