Zerowidth CleanerAll measured guides

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.

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

Open the Claude text cleaner · Full measured data

Code-point comparison for Encoded marker

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.

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
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":"A​B","decodeAccepted":true}
{"hash":"#A%E2%80%8BB","decoded":"A​B","decodeAccepted":true}
Literal marker
direct string / keep / invisible true
"https://example.com/#A​B"
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":"A​B","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.cjs

Reference 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.