About this tool

What it does, what it deliberately cannot do, what happens to your text, and how to use it.

What markcheck is

Some characters occupy real bytes but draw nothing on screen — or draw something that looks exactly like an ordinary space. A zero-width space between two letters. A narrow no-break space standing in for a normal one. A right-to-left override that silently reverses the words after it. You cannot see them, copy them out, or count them by eye.

markcheck reads the bytes and tells you what is present, exactly where, and what it is. It can hand back a cleaned copy. That is the whole product.

Watermarking

Hidden characters inserted to mark a document's origin or track who leaked it.

Steganography

Payloads encoded in invisible code points, riding inside ordinary prose.

Trojan Source

Bidirectional controls that make source code read differently to a human than to a compiler (CVE‑2021‑42574).

Prompt injection

Instructions smuggled to an AI model in the invisible Unicode Tags block.

What happens to your text

Nothing. It never leaves this page. The scan runs in your browser, in JavaScript, on your own machine. You can disconnect from the internet and it will still work.

This page declares connect-src 'none' in its Content-Security-Policy. That is not a promise, it is an instruction to your browser: the page is forbidden from making a network request, and the browser enforces it even if the code tried.
Files you drop or open are read locally with the browser's own FileReader. They are not uploaded.
Exported reports are built in the page and handed straight to your downloads.
No text, no filenames, no results, and no analytics about what you scanned are transmitted, stored, or logged anywhere.

The one thing that does touch a server

The visit counter in the footer. It is incremented on the server before the page is sent to you, which is precisely why the page itself still makes no network request. What gets stored is a single number — no IP address, no browser details, no per-visitor record, no timestamp. There is nothing kept that could connect a visit to a person, or to anything you scanned.

Prefer to take none of this on faith? Open your browser's network tab and use the scanner. You will see no requests. Or download index.html and open it with your wifi off.

What it cannot do

Three different techniques get called "AI watermarking." Only one of them puts anything in the bytes, and that is the only one a byte scanner can ever find.

TechniqueWhere it lives markcheck
Hidden characters In the text itself, as real code points Detects this
Statistical watermarks In which ordinary words a model chose — no characters added Cannot detect, and neither can any byte scanner
C2PA provenance A signed manifest in an image or PDF container Out of scope — use c2patool or exiftool
A clean result means "no hidden characters." It does not mean "not AI-generated." A statistical watermark leaves nothing in the bytes to find, so a clean scan says nothing either way about how the text was written. Rewriting text in your own words removes that class entirely.

Also deliberately out of scope

How to use it

1. Get your text in

It scans as you type. Check text forces a rescan.

2. Read the verdict

The large number is how many hidden characters are in scope. The bar underneath shows how they split by severity, and the table lists each one with its line, column, code point, and name. Below that, a preview of your text with every hit marked in place, so you can see exactly where they sit.

3. Narrow it down

Suspicious only hides the hits markcheck has annotated as likely legitimate — a byte-order mark at the start of a file, a joiner holding an emoji together, French punctuation spacing. Turn it on when you only want the things worth explaining.

4. Clean it

Copy cleaned puts a cleaned copy on your clipboard. Before / after shows you what would change first.

Cleaning respects Suspicious only. With it on, a family emoji keeps the joiner that holds it together while the zero-width space hiding in your paragraph is still removed. With it off, everything tracked is removed — which is thorough, but can break emoji sequences and some script shaping.

Keyboard

⌘K / Ctrl K Command palette — every action, searchable
⌘↵ / Ctrl ↵Scan now
EscClear the text box

Reading the results

Every hit is rated, so a watermark does not get lost among emoji joiners.

infoAnnotated and almost certainly legitimate: a BOM at the start of a file, an emoji joiner, a presentation selector, French spacing, digit grouping.
lowNonstandard whitespace with no particular explanation. Genuinely common in word-processor and typeset text.
mediumUnexplained invisibles: zero-width characters, format controls, variation selectors, direction marks. Nothing accounts for these in ordinary prose.
highThe classes used in attacks: bidirectional overrides and isolates that reorder what you read, and the Tags block used for smuggling and prompt injection.

Categories

Six by default — zero-width, invisible format, bidi, variation selector, tag, and nonstandard whitespace — plus a seventh, opt-in category in the command line tool covering every remaining Unicode default-ignorable code point, for forensic work.

Context matters more than the count. Nonstandard whitespace is ordinary in professionally typeset documents; bidirectional controls are ordinary in Arabic and Hebrew. That a character is present is a fact. What it means is your call, which is why markcheck shows you the evidence instead of a verdict.

The command line tool

The same scanner, for files, pipelines, and CI. One Python file, standard library only, no dependencies.

pip install markcheck
markcheck essay.md

Exit codes make it a linter: 0 clean, 1 hidden characters found, 2 a usage or I/O error.

markcheck *.md --suspicious-only     # skip the benign annotations
markcheck notes.md --strip           # write notes.clean.md
markcheck report.md --json           # machine-readable output
cat file.txt | markcheck --strip --stdout > clean.txt

This page is generated from the same character tables the command line tool uses, and a parity test proves the two agree on every hit, position, name, note, severity, and cleaned result.

Why you can check rather than trust

A tool that inspects your writing for hidden marks is asking for a lot of trust. The point of the design is that you should not have to give much.

It is open source. MIT licensed, every line readable on GitHub, including the page you are reading.
The privacy claim is enforced, not asserted. connect-src 'none' is checked by your browser, not by us.
It has no dependencies. Standard library only, so there is no supply chain to compromise and nothing to audit but the tool itself.
It cannot drift. This page is generated from the Python module; a differential test runs both implementations over more than fifteen hundred cases and fails the build if they ever disagree.
Its coverage is checked against the specification. A frozen Unicode dataset is the reference the curated list is tested against, so a gap fails a test rather than sitting unnoticed.
It states its limits. Everything on this page about what markcheck cannot do is there because a tool that overstates its reach is worse than no tool.