Security

Built to be inspected.

This site is intentionally a small target: a read-only portfolio with no accounts, no tracking cookies and no client-side secrets. Here is exactly what runs, what is exposed, and how to report a problem.

Read-only by designNo tracking, ever

Controls

What is enforced

Every item below is implemented in this repository, not borrowed from a checklist.

01

Transport & headers

Every response is served over HTTPS with HSTS preload, a deny-by-default CSP, strict referrer policy and a locked-down Permissions-Policy.

  • CSP nonce + strict-dynamic, no unsafe-inline scripts
  • frame-ancestors 'none' blocks clickjacking
  • HSTS max-age 2 years with includeSubDomains; preload
02

Secrets management

The Roblox API key never reaches the browser. Environment variables are validated at boot and secret modules are import-guarded.

  • server-only import guard on all privileged modules
  • .env* gitignored, only .env.example is committed
  • No NEXT_PUBLIC_ prefix on any credential
03

Data pipeline

The site only talks to origins we chose. Universe IDs come from a server-side allowlist — never from a URL parameter or request body.

  • Zod validation on every upstream payload
  • Image URLs allowlisted to *.rbxcdn.com
  • 6s timeouts and bounded concurrency
04

API surface

One read-only endpoint, no state changes, no cookies and no CORS wildcard. Abuse is throttled per hashed client identifier.

  • GET-only handlers, generic error responses
  • 30 req/min per IP hash, 429 + Retry-After
  • Cache-Control: no-store on all responses
05

Network posture

When self-hosted, the app binds to loopback behind nginx. Only 80/443 are public; SSH is key-only and rate-limited; no database port exists.

  • ufw default deny, explicit 80/443 allow
  • Docker ports bound to 127.0.0.1
  • fail2ban on SSH and nginx limit_req
06

Supply chain

A deliberately small dependency tree, exact versions and a committed lockfile. No analytics SDKs, no tag managers, no third-party scripts.

  • Exact versions, lockfile committed
  • npm audit in CI, Dependabot enabled
  • Zero client-side third-party code

Privacy

What this site does not do

The safest data is the data you never collect. This site is a static-first portfolio wrapped in a thin read-only API.

  • No analytics, pixels or tag managers.
  • No advertising or cross-site trackers.
  • No tracking or advertising cookies, no accounts and no login surface. The only cookies are the optional theme and language preferences you set yourself.
  • No player data, IP logs or profiling.
  • No write endpoints, uploads or forms that mutate state.

Verification

Check it yourself

These are the same checks used before deploy. Run them against your own copy — no credentials required.

# 1. Response headers
curl -sI https://your-domain.com | grep -iE \
  'content-security-policy|strict-transport-security|x-frame-options|permissions-policy'

# 2. Public ports (expect only 80 and 443)
nmap -Pn -p- --min-rate 2000 your-domain.com

# 3. Rate limiting (expect some 429s)
for i in $(seq 1 40); do
  curl -s -o /dev/null -w "%{http_code}\n" https://your-domain.com/api/games
done | sort | uniq -c

# 4. Dependency audit
npm audit --omit=dev

A public website must keep 443 open — “all ports closed” would mean the site is offline. The goal is a minimal surface: 80, 443 and an admin-only SSH port, nothing else.

Responsible disclosure

Found something?

Email a description, reproduction steps and impact. Please do not open a public issue or test against other players. We acknowledge within 72 hours and aim to remediate confirmed issues within 30 days.

Machine-readable contact: /.well-known/security.txt