Litmus drives a real headless browser against user-submitted URLs and persists data per user, so it's built with a few specific threats in mind. This document describes the protections in place and how to report issues.
Please don't open a public issue for security problems. Email the maintainer instead and allow reasonable time for a fix before disclosure.
The server fetches whatever URL a user submits (Playwright navigation + the broken-link checker). Without guards, an attacker could point it at internal services or the cloud metadata endpoint (169.254.169.254 → IAM credentials).
- Every submitted URL passes
assertPublicUrlbefore the browser launches: it rejects non-http(s)schemes, blockslocalhost/*.local/*.internal/metadata hostnames, resolves the hostname and rejects any private/loopback/link-local/reserved IP (IPv4, IPv6, and IPv4-mapped). - The broken-link checker skips any link pointing at a private host.
- For local development you can opt out with
LITMUS_ALLOW_PRIVATE_HOSTS=true.
- The Supabase service-role key is server-only (
SUPABASE_SERVICE_ROLE_KEY, neverNEXT_PUBLIC_). The public anon/publishable key is safe in the browser only because RLS is enforced. - No secrets are committed —
.env.localis gitignored; only.env.example(placeholders) is tracked. - Errors are sanitized before reaching clients; raw stack traces and internal details are logged server-side only.
Row Level Security is enabled on every user-owned table (profiles, runs, shared_reports). A user cannot read or modify another user's data. Public shared reports are readable by id; the view-count bump uses a security definer RPC so anonymous viewers can't write to the table.
- Rate limiting on the test (
/api/test), crawl (/api/crawl), and share (/api/share) endpoints (in-memory; a shared store is the multi-instance scaling step). - Open-redirect protection on the OAuth callback —
nextis resolved to a same-origin relative path. - Path-traversal-safe local video route — the id is stripped to
[a-z0-9-]. - No XSS sinks — all tested-page content is rendered through React (escaped); there is no
dangerouslySetInnerHTML. The tested page's HTML is only inspected, never executed as the app's own markup. - Security headers on every response (
X-Content-Type-Options,X-Frame-Options,Referrer-Policy,Strict-Transport-Security,Permissions-Policy);X-Powered-Byis disabled and the unused Next image optimizer is turned off. - Auth is handled by Supabase with secure HttpOnly cookies via
@supabase/ssr; no hand-rolled JWTs.
npm audit reports issues in dev-only / transitive dependencies whose only fixes are breaking major upgrades:
esbuild/vite/vitest— affects the test dev server only, not production.globviaeslint-config-next— theglobCLI command-injection; Litmus never invokes the glob CLI.next— image-optimizer DoS (mitigated here by disabling the optimizer) and an RSC DoS; keep Next updated on a maintained release for production.
These are tracked and revisited as non-breaking fixes become available.