Edge watermark router
The watermark router is a tiny Cloudflare Snippet that runs ahead of the
thumbnail Worker. It is the deterministic watermark control point: a
watermark is never written into the bare URL’s cache — it is a 307 redirect
to a distinct wm.jpg key, so the bare URL only ever holds clean bytes.
Flipping a host onto watermarks is additive: add the host to WM_DOMAINS
and pin its current ids into the hot set. Because the redirect also skips the
metered Worker (and its KV reads), every flip is a cost saving, not a cost.
Everything else passes through untouched.
Behavior
Section titled “Behavior”The Snippet evaluates a shallow decision tree (it has a ~5 ms CPU budget, so no heavy logic):
- Paying customer? If the client IP or referrer domain is in the paying set, pass through to the Worker (which serves a clean image). Paying customers are never short-circuited.
- Malformed path
/.jpg?308redirect to the sharederror.jpg. - Borås WordPress loop? If the exact loop predicate matches (video ID, UA,
self-referrer, ASN
14061),307to that video’swm.jpg. See Redirects. - Hot-set short-circuit? If the request is
GET/HEADfor a hot video ID from a whitelisted watermark referrer (and not a paying domain/IP),307tohttps://cache.vumbnail.com/thumbnails/<videoId>/wm.jpg. - Otherwise pass through to the Worker.
Watermark via redirect
Section titled “Watermark via redirect”The router never rewrites bytes. Watermarking a request means answering it with
a 307 to a distinct wm.jpg cache key — the bare thumbnail URL is never
asked to hold watermarked bytes, so no shared edge cache can pin wm where a
clean image belongs. This is what makes the mechanism deterministic: the
decision is re-evaluated per request at the edge instead of being frozen into a
referrer-blind cache entry, and a host flip (or unflip) takes effect as fast as
the Snippet deploy propagates.
The hot set is a performance pin, not a policy boundary
Section titled “The hot set is a performance pin, not a policy boundary”Cold ids from a flipped host still pass through to the Worker, which applies the same referrer-cohort decision on its miss path — the same watermark outcome, just metered and cache-dependent. The hot set only decides where the decision is served (a deterministic, unmetered redirect at the edge), not whether the host is watermarked.
Why a Snippet instead of Worker code
Section titled “Why a Snippet instead of Worker code”Snippets run before Workers in Cloudflare’s pipeline and are far cheaper per
request. The hot set is a hand-maintained list of the most-requested
watermark-eligible video IDs whose wm.jpg is pre-generated in R2. Serving those
via a redirect skips a metered Worker invocation and its KV reads. At current
volume the blended saving is on the order of $1.07 per million requests
diverted; the Snippet pays for itself immediately.
Protecting clean images
Section titled “Protecting clean images”Because cache.vumbnail.com is a public R2 domain, a WAF rule blocks direct
fetches of any …/clean.jpg path, so the public bucket can only ever serve the
watermarked or shared objects.
Inputs & outputs
Section titled “Inputs & outputs”Inputs: request method, path, Referer, CF-Connecting-IP /
True-Client-IP / X-Forwarded-For, User-Agent, and Cloudflare ASN.
Outputs: a 307/308 redirect to a public R2 object, or pass-through to the
thumbnail Worker. The Snippet itself never renders or fetches an image.
States & edge cases
Section titled “States & edge cases”- Hot ID without a pre-rendered
wm.jpg— theverify-hotsetstep ensures every hot ID has its watermarked object in R2 before deploy; a missing object would redirect to a 404, so the hot set and R2 are kept in sync. - Hot ID ages out of the set — the request falls through to the Worker, which makes the same cohort decision on miss. Coverage holds; determinism (and the cost saving) is what decays. Rotating-catalog hosts decay fastest — see hot-set-management.
- Dev/local referrer — dev and local traffic is served clean by policy,
detected by referrer host (
localhost,127.0.0.1,[::1],*.local,*.test, RFC-1918 ranges, tunnel hosts) — never by user-agent. The explicit early return (isDevCleanReferrer) is Planned; today these referrers simply never matchWM_DOMAINSand pass through. - Size / bundle limits — the deployed Snippet must stay within a 32 KB bundle
and 5 ms CPU; it uses plain
Setlookups and avoids regex. Current usage is ~10.5 KB. - Paying customer on a watermark referrer — the paying check runs first, so they pass through to a clean image even if the referrer is whitelisted.
- Rollback — deploys take a snapshot first;
rollback:snapshotrestores the previous Snippet and rule. Restoring clean delivery for a flipped host is the dual-lane procedure above, not a Snippet rollback alone — see reverting-deploys. - Deploy / verify / smoke —
bun --filter @repo/snippet deploy,verify-hotset, andsmokeare the operational commands;lintenforces the size budget.
Data shape
Section titled “Data shape”// Hand-maintained constants in watermark-router.snippet.jsconst HOT_VIDEO_IDS: Set<string> // ~68 most-requested watermark-eligible idsconst WM_DOMAINS: Set<string> // referrers that trigger watermarking: // the always-on self-referred hosts // S01–S04, plus flipped rollout hosts // (H01, …) — a flip is additiveconst PAYING_DOMAINS: Set<string> // e.g. C01's domain → always pass throughconst PAYING_IPS: Set<string> // e.g. C01's entitled IP → always pass throughconst BORAS_WORDPRESS_VIDEO_IDS: Set<string> // the two pinned loop idsconst BORAS_ASN = 14061
type SnippetOutcome = | { action: "passthrough" } | { action: "redirect"; status: 307 | 308; location: string }Open questions
Section titled “Open questions”- How far to expand the hot set. Larger hot sets (e.g. a
top200-per-hostslice) save more Worker/KV spend but grow the bundle toward the 32 KB ceiling (~22.5 KB for the largest candidate). The coverage-vs-size trade-off is open, and the hot-set refresh automation is not built — rotating-catalog hosts decay toward ~0% hot coverage in ~8 weeks without it. See hot-set-management. Cache-Control: no-storeon the307— required before flipping any host above ~1M requests/month, so intermediaries cannot pin the redirect itself.isDevCleanReferrerearly return — the explicit dev/local clean short-circuit is Planned, not built.- No-referrer policy at the edge — no-referrer → clean is decided; the written sign-off document is still open. See Freemium watermarking.
- Keeping
PAYING_DOMAINS/PAYING_IPSin sync with the Worker’s KV entitlement projection (the Snippet list is currently hand-maintained). Tracked on the global Open questions.