Runtime security approach
The vanityURLs runtime is deliberately small: validate the generated registry, serve static assets, read v8s.json, and return one of a small set of outcomes.
For the design rationale, read Runtime security for a small redirector. This page is the compact reference for controls to preserve.
Defensive runtime
The Worker keeps the runtime path narrow:
- only public
GET,HEAD, and quietOPTIONSrequests are accepted, plusPOST /lookup/resolvefor lookup resolution and the dedicatedPOST /_analytics/lookupbeacon - direct access to
/v8s.json,/v8s-blocklist.json, and/v8s-site-config.jsonreturns 404 - redirects allow only
http:andhttps:targets - redirect targets with credentials, missing hostnames, control characters, or unsupported protocols fail closed
- splat values are URL-encoded segment by segment before insertion
- lifecycle states resolve through explicit routing rules
- protected operational paths verify Cloudflare Access JWTs and fail closed when Access is not configured
- scanner probes return a plain no-store 404 before short-link lookup or analytics
- analytics is sent with
ctx.waitUntil()so provider failure does not delay redirects
Default runtime protections include:
- non-HTTP(S) protocols
- credentials embedded in URLs
- localhost,
.localhost, and.localtargets - private, loopback, reserved, multicast, and documentation IP ranges
- known public shorteners used for redirect chains
- local examples of phishing lure domains
- high-risk executable download extensions such as
.exe,.scr,.bat,.cmd,.msi,.ps1,.vbs, and.jar
Resolution order
For each request, the Worker follows a deliberately narrow path:
- Reject raw runtime assets and known scanner probes
- Accept only
GET,HEAD, andOPTIONSfor public routes, plusPOST /lookup/resolveandPOST /_analytics/lookup - Normalize the incoming path
- Look for an exact link
- If no exact link matches, look for a splat link
- Apply schedule and lifecycle state
- Return a redirect, an informational page, or a 404
Schedules only apply to exact links. Splat links are useful for stable namespaces, but they should not be used for time-sensitive redirects.
Build-time guardrails
npm run check builds the same assets used for deployment, validates the generated registry, validates policy files, lints the repository, and runs Worker tests.
Validation verifies that link rows have the expected shape, URL targets normalize safely, unsafe targets are rejected, splat aliases do not shadow unsafe parent paths, schedules are valid, generated runtime assets use the expected schema, raw runtime assets stay unreachable, and generated src/ matches the Worker source in scripts/workers/.
The generated registry and runtime policy are treated as data, not executable code. Local instance changes belong in custom/; product defaults stay in defaults/; canonical Worker source stays in scripts/workers/; generated src/ is only for Wrangler compatibility. That keeps updates reviewable and makes rollback a normal Git operation.
Default response headers include X-Generated-By: vanityURLs.link, no-index rules, host-scoped HSTS, nosniff, clickjacking protection, referrer and permissions policies, and a CSP that blocks inline JavaScript and inline CSS. If you override custom/public/_headers, keep that generator identity, compatible cache and security rules, and the raw runtime-file blocks unless you have a deliberate public-disclosure reason.
Operational file guards
Cloudflare Access is not the only layer that limits operational file access. Keep controlled access on localized stats paths such as /en/_stats/ and /fr/_stats/, plus /_tests, the _headers runtime-file entries, and the Worker runtime-file guard enabled unless you have a deliberate public-disclosure reason.
| Control | Paths | What it does |
|---|---|---|
| Worker private runtime asset guard | /v8s.json, /v8s-blocklist.json, /v8s-site-config.json | Returns 404 for direct public requests |
Static _headers fallback | /v8s.json, /v8s-blocklist.json, /v8s-site-config.json, /*/_stats/*, /lookup/* | Adds no-cache and no-index headers if static assets are served directly |
| Protected stats API | /en/_stats/api/v8s.json, /<lang>/_stats/api/v8s.json | Exposes the generated registry only through the protected stats surface |
| Reserved slug validation | /_stats, /<lang>/_stats, /api, /_worker, /v8s.json, /v8s-blocklist.json | Prevents short links from being created under reserved operational paths |
Cloudflare edge controls
Cloudflare should reject commodity abuse before the Worker runs. Use Network protection for the operator workflow around WAF custom rules, rate limiting, Bot Fight Mode, AI crawler controls, Browser Integrity Check, managed rules, and related domain settings. Use Access control for private operational paths.
Keep the Worker blocklist as the fallback, not the first line of defense for high-volume abuse. The canonical WAF, AI crawler, Rules, Network, DNS, SSL/TLS, Security, Caching, and Cloudflare analytics settings live in Network protection.