Security overview
- Secrets live only in the Cloudflare Worker (Worker secrets): GNews, Unsplash, Beehiiv, NewsData, the Neon connection string, the Clerk secret key and webhook signing secret, and the key that encrypts provider keys stored through the admin console. Anything prefixed
VITE_is public and never holds a private key; the only Clerk value in the website is the publishable key, which is public by design. - Sessions are Clerk session tokens (short-lived JWTs). The Worker verifies each one (signature, expiry, and that it was issued for a NovaReport origin) before touching account or admin data.
- Administrators are decided by the
rolecolumn in the Neonapp_userstable, checked on every admin request. The role can only be changed by an operator with database access (npm run role:grant, which requires a reason and writes an audit entry). Signing up, an email address, Clerk metadata, request bodies, the first account, or old Supabase roles never grant it. - Admin actions are written to
admin_audit_logs(who, what, which record, outcome, request ID). Provider API keys stored through the console are encrypted with AES-256-GCM and never returned to the browser. - Clerk webhooks are accepted only with a valid Svix signature less than 5 minutes old, and are idempotent.
- The admin request tester only calls
https://URLs under a registered provider's base URL, after resolving away..segments; it never follows redirects. - No public CORS proxies are used in production. The Worker accepts browser calls only from novareport.net origins (and local development ports), and rate-limits by IP.
- Article links are sanitized before rendering.
- Passwords are handled by Clerk. When old accounts are moved from Supabase, only their existing bcrypt hashes are transferred — never plaintext.
Reporting a vulnerability
Do not open a public issue. Email security@novareport.net with a description, impact, and steps to reproduce.