Start with what is impossible
Screen capture happens in the operating system. A web page has no API that can see it, block it or be told it occurred. Disabling right-click, trapping PrintScreenand blanking the page on window blur are all defeated by a second device pointed at the monitor, and most are defeated by the browser's own reader mode. Any vendor selling screenshot prevention in a browser is selling a deterrent.
What remains is attribution. If a page reaches somewhere it should not, the question stops being whether it could have been copied and becomes whose session produced the copy. That question has a measurable answer, so it is the one worth testing.
- CSS overlay drawn in the browserbypassable
One element in the DOM. Delete it in developer tools and the clean page is underneath, because the clean page was already downloaded.
- Canvas drawn from client statebypassable
Still client-side. The unmarked source image was served, so it is already in the browser cache and reachable from the network panel.
- Composited into the page image on the serverenforced
The bytes that leave the server are already marked. There is no element to delete and no clean copy to reach.
Method
Pages are rasterised at 150 DPI to 1240 by 1755 WebP images. The identity mark is composited onto the pixel buffer and only then encoded, so the encoded bytes are what any bypass retrieves. The mark is tiled at a 30 degree rotation with a half-pitch stagger on alternate rows, and each glyph gets a one-pixel halo in the opposite luminance direction so it stays readable over both dark text and white paper. The room can set opacity, but the burner clamps it up to a floor of 25, so a room configured to 1 renders byte-identical to a room configured to 25.
The test itself:
- Render a synthetic memo page at the exact geometry the rasteriser emits, WebP quality 85.
- Pipe it through the production burner with a four-line identity: name, email, IP address, UTC timestamp.
- Take the per-pixel absolute difference between the marked and clean images. That difference is the watermark, since the two share a source and an encoder.
- Sweep 64 crop windows on an 8 by 8 grid, each 25% of the page width and 25% of its height, 310 by 438 pixels. Score each window as the fraction of pixels differing by more than 12 levels.
- Repeat the sweep after JPEG quality 50, after a 50% downscale, and after both.
The 64 windows exist so the reported number is not the average case. An attacker crops where the mark is thinnest, so the figure that matters is the minimum across all 64, not the mean.
One control first. Encoding the clean page through WebP quality 85 a second time, with no burn at all, produces a maximum window score of 0.0022%. Re-encoding noise is three orders of magnitude below the signal, so what the sweep measures is the mark.
Reproducing it
The burner is infrastructure/hetzner/worker/src/watermark.py and it is a command-line program that reads an image on standard input. It needs Python 3 and Pillow.
python3 watermark.py --text "Sara Al-Otaibi sara@example.com IP 203.0.113.42 2026-08-04 09:14 UTC" \ --position tiled --opacity 15 --color gray \ --min-crop 0.25 --quality 85 < page.webp > marked.webp
The full sweep, including the assertions that a blank identity is refused and that two participants receive different bytes, lives in tests/rasterization/watermark-burn.test.ts.
Results
| Criterion | Minimum | Mean | Maximum |
|---|---|---|---|
| No further processing | 11.6% | 15.0% | 18.6% |
| JPEG quality 50 | 13.4% | 16.9% | 19.8% |
| 50% downscale | 11.8% | 15.3% | 19.8% |
| JPEG q50 then 50% downscale | 11.7% | 15.2% | 20.0% |
JPEG quality 50 raises the score rather than lowering it, because lossy compression spreads each glyph edge across neighbouring pixels and more pixels end up differing from the clean reference. That is an artefact of the measure, not extra durability. The number to read is the downscale row, where the mark loses half its resolution and still holds.



Two viewers, two files
Because the mark is composited per request, two participants opening the same page receive different bytes. That is what makes the mark evidence rather than decoration: a leaked image can be matched back to one session. If the burn fails for any reason, the page proxy returns 503 and never falls back to the clean image, and a blank identity string is refused outright rather than producing an unattributable page.
The watermark is one half of the answer. The other half is the audit trail, which records which participant opened which page and when, so a marked image found in the wild has a matching log entry to confirm it against.
Related: Where a permission filter has to live · How we test our own data room · The spreadsheet fidelity test · Pricing
Watermarking is on every paid tier. See what each tier includes.