How Morphix is protected in production, and how to reproduce each layer. This is a reference for others building a similar "obfuscated frontend + protected backend behind Cloudflare" setup.
Honest disclaimer first: none of the frontend measures are "real" security. Anything the browser downloads can be read by a determined person. These layers raise the effort/cost of casual copying, scraping, and abuse — the actual security boundary is the backend, which holds all logic and validates all input.
The entire frontend (frontend/build.mjs) is run through
javascript-obfuscator:
control-flow flattening, RC4 string-array encoding, renamed globals, dead-code
injection, self-defending, and debug protection. Function names, endpoint
strings, and logic become unreadable _0x… output. The readable source lives on
GitHub; only the obfuscated build (dist/) is ever served.
disable-devtool (vendored in
frontend/weblib/, no CDN dependency) is injected into the production
index.html. It disables the right-click menu and, if devtools is opened, blanks
the page and redirects away. It cannot block view-source: (that protocol runs no
JS) — which is why layer 3 exists.
The served HTML is a bare shell: <div id="app-root"></div> plus the script tag.
All page markup lives inside a template string in the obfuscated app.js and is
injected at runtime. So view-source: on the page reveals nothing, and the
markup is also RC4-encoded inside the obfuscated bundle. view-source: can never
be fully blocked on the web — the goal is to make it show nothing useful.
The public URL is a Cloudflare Pages project (pages/_worker.js) that
reverse-proxies every request to the backend. Visitors only ever see
Cloudflare's edge — the backend's host/IP is never exposed to the browser, and
Cloudflare provides TLS and DDoS mitigation in front. The proxy forwards
CF-Connecting-IP so the backend can still rate-limit by real client IP.
Cloudflare hides the origin from browsers, but scanners (Censys, Shodan, etc.)
can still find the backend's own URL and hit it directly, bypassing the proxy. To
stop that, the Pages proxy attaches a secret header X-Origin-Auth: <PROXY_SECRET>,
and the backend (backend/turnstile.go → originAllowed) rejects any request
that lacks it with 403 — except /api/health, which stays open for uptime
checks. Result: the backend only answers requests that came through your
Cloudflare proxy. Set the same PROXY_SECRET value on both sides (see below).
When configured, an invisible Turnstile
widget runs on the page and issues a token; the backend verifies it with
Cloudflare's siteverify before every conversion or URL fetch. This blocks
automated abuse of the API without showing a captcha to real users. It is
env-gated — off unless a sitekey/secret is set.
The backend (backend/ratelimit.go) enforces a token-bucket rate limit per
client IP on /api/convert and /api/fetch (real IP via CF-Connecting-IP),
returning 429 with Retry-After when exceeded. A global semaphore caps how
many conversions run at once so a burst of large media can't exhaust the CPU.
Tunable via flags: -rate, -burst, -maxjobs.
/api/fetch downloads remote files server-side. It refuses non-http(s)
schemes and blocks connections to loopback, private, and link-local addresses at
dial time (checked on the resolved IP, so DNS rebinding can't bypass it), with a
size cap and timeout.
Layers 1–4 and 7–8 are always on in the production build. Layers 5 and 6 are env-gated — turn them on like this:
- Pick a long random string as
PROXY_SECRET. - Set it as a Cloudflare Pages environment variable (project → Settings →
Variables and Secrets), or
wrangler pages secret put PROXY_SECRET. - Set the same value as
PROXY_SECRETon the backend host (Render → your service → Environment). - Redeploy both. Direct hits to the backend now return 403; the proxied site works normally.
- Cloudflare dashboard → Turnstile → add a widget in Invisible mode for your Pages hostname. Copy the sitekey and secret.
- On the backend host, set
TURNSTILE_SITEKEY(the sitekey) andTURNSTILE_SECRET(the secret). - Redeploy the backend. The widget auto-loads and every conversion/fetch is verified. With the keys unset, the feature stays off and nothing changes.
Custom Cloudflare WAF / firewall / rate-limiting rules require your own
domain added to Cloudflare as a zone. A *.pages.dev URL is Cloudflare-managed
and has no zone you can attach rules to. Cloudflare still applies its baseline
bot/DDoS protection to Pages, and the origin lockdown (layer 5) + app rate
limiting (layer 7) cover direct-access and abuse. To get custom WAF rules, put
the site on your own domain (add it to Cloudflare, point it at the Pages project,
and create rules on that zone).