CLAUDE WATERMARK REMOVER · PRACTICAL TEST
Does a URL fragment getter decode an invisible percent escape?
Our URL.hash getter keeps %E2%80%8B encoded. An explicit decodeURIComponent step exposes U+200B; cleaning the encoded URL alone leaves that escape in place.
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. Three destination checks use separately recorded browser observations; those pages explain the distinction. 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 |
|---|---|---|---|
| Encoded marker direct string / keep / invisible true | "https://example.com/#A%E2%80%8BB"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+0063 U+006F U+00 … | "https://example.com/#A%E2%80%8BB"No selected invisible characters found | {"hash":"#A%E2%80%8BB","decoded":"AB","decodeAccepted":true}{"hash":"#A%E2%80%8BB","decoded":"AB","decodeAccepted":true} |
| Literal marker direct string / keep / invisible true | "https://example.com/#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+0063 U+006F U+00 … | "https://example.com/#AB"Removed 1 invisible character | {"hash":"#A%E2%80%8BB","decoded":"AB","decodeAccepted":true}{"hash":"#AB","decoded":"AB","decodeAccepted":true} |
| Encoded space direct string / keep / invisible true | "https://example.com/#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+0063 U+006F U+00 … | "https://example.com/#A%20B"No selected invisible characters found | {"hash":"#A%20B","decoded":"A B","decodeAccepted":true}{"hash":"#A%20B","decoded":"A B","decodeAccepted":true} |
| Malformed escape direct string / keep / invisible true | "https://example.com/#A%ZZB"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+0063 U+006F U+00 … | "https://example.com/#A%ZZB"No selected invisible characters found | {"hash":"#A%ZZB","decodeAccepted":false,"errorName":"URIError"}{"hash":"#A%ZZB","decodeAccepted":false,"errorName":"URIError"} |
The getter and decoder are different operations
The encoded-marker fixture returns a hash containing the percent escape. The observer then explicitly decodes the text after the hash sign, yielding A, U+200B and B. Cleanup of the original URL does not change that encoded spelling. With a literal marker in the source URL, cleanup removes the marker and the later hash becomes AB. The encoded-space reference decodes to a space at both stages, while the malformed percent escape remains in the hash and throws URIError when explicitly decoded. We record both operations instead of calling either result simply the fragment.
A parser does not promise decoded application data
The URL hash API serializes fragment data; the destination application may choose a further interpretation. Our experiment uses a fixed example.com URL and performs no navigation or network request. It therefore does not measure anchor matching, router behavior or a web application accepting this fragment. The earlier path percent-encoding page cannot settle this getter question because path and fragment processing are separate API surfaces. The homepage has no fragment decoder and no rule for malformed percent escapes. A successful URL construction only establishes that this parser accepted the complete string.
Keep fragment interpretation explicit
Preserve the original URL when diagnosing a failed anchor or application route. Read the fragment using the destination parser, identify whether the application expects raw or decoded text, and catch malformed decoding deliberately. Inspect the decoded sequence before removing an unwanted selected character. Serialize through the intended URL API and test the actual application afterward. Do not decode repeatedly until a string looks right; another percent sequence can represent intentional data at a later layer. The measured matrix explains these four fragments and the cleanup boundary, without promising that every router or fragment syntax follows the same policy.
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 URL Standard 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.