Security and headers
The protections the theme ships with, and the one line you must remove before going live.
All of this lives in next.config.ts at the root of the project. You do not need
to understand every line, but there is one change every customer should make.
Remove the development origins
Do this before you go live.
allowedDevOrigins: ["100.117.62.95", "100.91.160.187"], Those are private network addresses from the theme author's development machine. They do nothing for you. Delete the line entirely, or replace the addresses with your own if you develop across a local network.
It only affects the development server, not production — but leaving somebody else's addresses in your configuration is untidy at best.
Allowed image hosts
Next.js refuses to load and optimise images from hosts you have not listed. The theme lists three — two fixed, and your own Supabase project:
| Host | Why |
|---|---|
dunb17ur4ymx4.cloudfront.net | Package images served by Tebex |
mc-heads.net | Player avatar renders |
yourref.supabase.co | Your uploaded logo and favicon |
The Supabase one differs per install, so it is derived from SUPABASE_URL at build
time rather than hardcoded:
const supabaseHost = supabaseHostname(); // parsed from SUPABASE_URL
images: {
remotePatterns: [
{ protocol: "https", hostname: "dunb17ur4ymx4.cloudfront.net" },
{ protocol: "https", hostname: "mc-heads.net" },
...(supabaseHost ? [{ protocol: "https" as const, hostname: supabaseHost }] : []),
],
},This is why SUPABASE_URL must exist at build time. If it is missing during the
build, the Supabase host never reaches this list or the Content-Security-Policy, and
your uploaded logo is blocked in the browser — even though the site otherwise works
and the admin panel uploads fine.
Set it, then trigger a new build. Editing the variable alone changes nothing.
To use images from anywhere else — your own CDN, say — add its hostname to that list and rebuild:
{ protocol: "https", hostname: "cdn.yourdomain.com" },The security headers
Sent with every response:
| Header | What it does |
|---|---|
X-Content-Type-Options: nosniff | Stops browsers guessing file types, which is a classic attack route |
Referrer-Policy: strict-origin-when-cross-origin | Does not leak your full URLs to other sites |
X-Frame-Options: SAMEORIGIN | Stops other sites embedding your store in a frame |
Permissions-Policy | Turns off camera, microphone and location access |
Strict-Transport-Security | Forces HTTPS. Production only |
Content-Security-Policy-Report-Only | See below |
About Strict-Transport-Security
Once a browser sees this header, it refuses to talk to your domain over plain HTTP for two years. That is good for a live store, and it is why the header is only sent in production — you do not want it stuck on a domain you are still testing.
The Content Security Policy
A CSP restricts where a page may load scripts, styles and images from, which is the main defence against injected scripts.
The theme ships it in report-only mode:
{ key: "Content-Security-Policy-Report-Only", value: cspReportOnly },Report-only means violations appear in the browser console but nothing is blocked. That is deliberate: the admin panel and the theme's inline dark-mode script both need careful handling, and an enforcing policy that is slightly wrong takes your site down.
Should you turn on enforcement?
Only if you are comfortable debugging it. If you want to:
Watch for violations first
Browse your live site — home page, store, basket, checkout, and the whole admin panel — with the browser console open. Note every CSP violation.
Widen the policy to cover them
Add the hosts and directives your site genuinely needs to the cspReportOnly array
in next.config.ts.
Only then switch the header name
{ key: "Content-Security-Policy", value: cspReportOnly },Test everything again
Especially the admin panel, which is the most likely thing to break.
If you add an image host to remotePatterns, add it to the CSP's img-src list
too, or you will get a violation once enforcement is on.
The cross-origin guard
Basket requests are rejected in production when they do not come from your own
site. This is why NEXT_PUBLIC_SITE_URL has to be exactly right — the check
compares the request's origin against it.
It is deliberately skipped outside production and when NEXT_PUBLIC_SITE_URL is
unset, so local development and testing are unaffected.
Getting 403 Cross-origin request rejected on a live site means the two do not
match: check www versus no www, and http versus https.
Keeping your secrets secret
TEBEX_TOKEN,SERVER_SECRETandSUPABASE_SERVICE_ROLE_KEYare only ever read on the server. Never rename them with aNEXT_PUBLIC_prefix — that ships them to every visitor's browser.- The Supabase
service_rolekey is the worst of the three to leak: it bypasses row-level security and grants full read/write access to your entire project. Rotate it in the Supabase dashboard if it is ever exposed. .env.localis in.gitignore. Keep it there, and checkgit statusbefore your first push.- Use a different
PAYLOAD_SECRETfor your live site than for your local copy. - Rotate a token immediately if you ever paste it somewhere public. Tebex lets you regenerate both from the Creator Panel.
Keeping things patched
Security fixes reach you through dependency updates:
bun update
bun run checkbun run check runs the linter, the type checker and a build, so you find out
before deploying if an update broke something.