2050planet.com โ Security Audit Report
**Date:** 29 April 2026
**Codebase audited:** `2050planet-app` (Next.js 14.2.18 + Supabase + Anthropic Haiku)
**Verdict:** ๐ก **YELLOW** โ clean foundation, two critical gaps must be closed before public launch
**Time to GREEN:** ~90 minutes with the drop-in files in `/drop-in/`
---
Executive summary
The codebase is well-built. Modern Supabase SSR pattern, RLS enabled on all tables, service role key stays server-side, errors don't leak details to the client, gitignore correctly excludes `.env*`. The `dangerouslySetInnerHTML` in `ArticleLayout.tsx` is safe because the markdown pipeline uses `allowDangerousHtml: false` and the content is trusted (authored by you, not user-submitted).
But there are **two critical issues** that, on a public 2050planet.com launch, would cause real damage:
1. **The TERRA endpoint has no rate limiting.** A single attacker (or a curious teenager with a curl loop) can hammer `/api/terra` and run your Anthropic bill into thousands of dollars overnight.
2. **The free-message limit is trivially bypassable.** The server only counts user messages in the *current request payload*, not historically. Anyone who clears cookies or sends fresh `messages: [oneMessage]` arrays gets unlimited free TERRA forever. This breaks both the cost model AND the sign-in incentive.
Plus three high-severity gaps in headers/CORS that will fail securityheaders.com.
All issues are fixed by the drop-in files in this package.
---
Findings by severity
๐ด CRITICAL
C1. No rate limiting on `/api/terra`
C2. Free-message limit bypassable
---
๐ด HIGH
H1. Missing CSP, HSTS, Permissions-Policy headers
H2. No origin/referer check on `/api/terra`
H3. `poweredByHeader` not disabled
---
๐ก MEDIUM
M1. Prompt injection can break TERRA brand identity
M2. `profiles` UPDATE policy missing `WITH CHECK` clause
M3. No bot detection on auth flow
M4. Image hostnames overly permissive
---
๐ก LOW
L1. Missing security.txt
L2. Children's data โ UAE PDPL note
L3. No `/api/terra` request size limit explicit
---
๐ข GOOD (already done correctly)
- โ `.env.local` correctly gitignored
- โ Service role key only used in `lib/supabase/server.ts` `createServiceClient()`
- โ Service role key never appears in any client component
- โ Errors return generic messages, not stack traces
- โ TERRA route has basic input validation (length, role shape)
- โ RLS enabled on `terra_usage` and `profiles`
- โ Owner-scoped policies on `terra_usage` and `profiles`
- โ `auto-create profile` trigger uses `security definer` correctly
- โ Markdown pipeline uses `allowDangerousHtml: false` (the `dangerouslySetInnerHTML` in `ArticleLayout.tsx` is safe)
- โ Middleware refreshes session on every request
- โ Next.js 14.2.18 (recent, no known critical CVEs as of audit date โ but run `npm audit` before launch)
---
Pre-launch dependency check
Run before deploy:
```bash
npm audit --audit-level=high
```
If any HIGH/CRITICAL CVEs appear, fix before launch.
Specifically watch for:
- `@anthropic-ai/sdk` โ keep on latest
- `next` โ patch updates ship security fixes; stay on 14.2.x latest
- `@supabase/ssr` and `@supabase/supabase-js` โ same
---
Files in this package
```
2050planet-security/
โโโ AUDIT-REPORT.md (this file)
โโโ PRE-LAUNCH-CHECKLIST.md (the day-of-launch tickbox)
โโโ THREAT-MODEL.md (filled in for 2050planet specifically)
โโโ drop-in/
โโโ next.config.js (REPLACE existing)
โโโ lib/
โ โโโ rate-limit.ts (NEW โ Upstash rate limiter)
โ โโโ terra-guards.ts (NEW โ prompt injection defence)
โโโ app/
โ โโโ api/terra/route.ts (REPLACE existing โ adds rate limit + persistent free counter + origin check + prompt injection guards)
โ โโโ .well-known/security.txt/route.ts (NEW)
โโโ supabase/migrations/
โโโ 0002_security_hardening.sql (RUN in Supabase SQL editor)
```
---
Apply order (do all six in sequence)
1. Replace `next.config.js` with drop-in version
2. Create `lib/rate-limit.ts` and `lib/terra-guards.ts`
3. Replace `app/api/terra/route.ts` with drop-in version
4. Add `app/.well-known/security.txt/route.ts`
5. Run `supabase/migrations/0002_security_hardening.sql` in Supabase SQL editor
6. Add three new env vars to Vercel: `UPSTASH_REDIS_REST_URL`, `UPSTASH_REDIS_REST_TOKEN`, `ALLOWED_ORIGINS`
Then: `npm run build` locally โ deploy to Vercel preview โ run the `PRE-LAUNCH-CHECKLIST.md` against the preview URL โ ship.