Skip to main content

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 role column in the Neon app_users table, 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.