Launching on Product Hunt means thousands of new users hitting your app on day one. You will have strangers poking at every endpoint, bots scanning for open ports, and security researchers checking your headers within minutes of going live. Before any of that happens, you need confidence that your infrastructure is solid. Not theoretically solid. Actually solid, tested, and verified.
We built SalesSheet as a solo-founder operation from day one. That means no dedicated security team, no DevSecOps hire, no third-party penetration testing budget. What we did have was a checklist. A concrete, item-by-item list of everything that needed to be true before we flipped the switch. This post is that checklist, explained in detail, so other founders building on Supabase and modern stacks can use it as a starting point.
Security is not a feature you add later. It is a foundation you build on from the start. Every shortcut you take before launch becomes a vulnerability you carry after launch.
This is the single most important item on the list. If you are calling OpenAI, Anthropic, or any AI provider directly from browser JavaScript, your API keys are visible to anyone who opens DevTools. It does not matter if you obfuscate them, embed them in environment variables at build time, or hide them behind a minified bundle. If the key reaches the browser, it is compromised.
We moved all AI provider keys into Supabase Edge Functions. The browser sends a request to our Edge Function endpoint, the Edge Function authenticates the user via the Supabase JWT, then makes the downstream API call with the key stored in Supabase Vault. The browser never sees the key. This pattern also gives us a natural choke point for rate limiting and usage tracking. Every AI call flows through a single gateway we control. If you are building an AI-powered product, this is non-negotiable.
Content-Security-Policy (CSP) headers tell the browser exactly which scripts, styles, images, and connections are allowed. Without CSP, an XSS vulnerability means an attacker can load any script from any domain. With CSP, even if they inject a script tag, the browser refuses to execute it because the source domain is not on the allowlist. We wrote about our CSP implementation in detail, but the short version is: set default-src 'self', then explicitly allowlist each third-party domain you actually use (Supabase, Sentry, Google Fonts, Stripe).
CORS allowlisting is the server-side complement. Our Supabase Edge Functions only accept requests from our production domain and localhost for development. Any request from an unapproved origin gets a 403 before it touches business logic. The combination of CSP (client-side restriction) and CORS (server-side restriction) creates a two-layer defense against cross-origin attacks that is surprisingly effective for the minimal configuration required.
CSP and CORS together form a two-layer defense. CSP stops the browser from loading unauthorized resources. CORS stops unauthorized domains from reaching your API. Neither alone is sufficient.
We use Supabase Auth with the PKCE (Proof Key for Code Exchange) flow. PKCE prevents authorization code interception attacks by generating a one-time code verifier and challenge pair for each login attempt. It is the recommended flow for single-page applications and mobile apps where you cannot safely store a client secret. Supabase handles the heavy lifting here, but you still need to verify that your auth configuration disables implicit flow and enforces PKCE for all login methods.
Input sanitization happens at two layers. First, we sanitize all user-provided text before storing it in the database. Contact names, deal notes, email body previews, custom field values, everything that comes from a text input gets run through DOMPurify before insertion. Second, we sanitize on display. Even if something slips past the write filter, the React rendering layer escapes HTML entities by default. This defense-in-depth approach means a single failure in either layer does not result in a stored XSS vulnerability. We also validate data types and lengths server-side in our Edge Functions so that malformed payloads never reach the database.
Supabase sits on top of PostgreSQL, which means you get Row Level Security (RLS) policies. RLS is the most underappreciated security feature in modern SaaS architecture. Instead of checking permissions in your application code (where a single missed check creates a vulnerability), you define policies at the database level. The database itself enforces who can read, insert, update, and delete each row.
For SalesSheet, our RLS policies ensure that users can only access data belonging to their organization. The policy checks the JWT's organization_id claim against the row's organization_id column. This means that even if an attacker bypasses our application layer entirely and sends raw SQL through the Supabase client library, they still cannot access another organization's data. We test our RLS policies explicitly in our test suite by attempting cross-org reads and verifying they return empty results.
One mistake we see in other Supabase projects is forgetting to enable RLS on new tables. Our CI pipeline includes a check that verifies every table in the public schema has RLS enabled. If a migration adds a table without a policy, the build fails.
Production hardening is not just about preventing attacks. It is about knowing immediately when something goes wrong. We use Sentry for error tracking with source maps uploaded at build time. When an unhandled exception occurs in production, we get a Slack notification within seconds that includes the original TypeScript stack trace, the user's browser and OS, and the breadcrumb trail of events leading up to the crash. This has saved us multiple times during high-traffic periods where a specific browser version triggered an edge case.
Web Vitals tracking monitors the three Core Web Vitals: Largest Contentful Paint (LCP), First Input Delay (FID), and Cumulative Layout Shift (CLS). We log these to our analytics pipeline and alert when any metric degrades beyond its threshold. CLS was particularly important for us because real-time updates can cause layout shifts if the new content changes the height of a row. We solved this with fixed-height row containers and the flicker elimination patterns we developed for our real-time sync system.
Monitoring is not optional for a production app. If you cannot measure it, you cannot fix it. Sentry plus Web Vitals gives you both crash detection and performance regression alerting for under $30/month.
We run over 375 automated tests before every deployment. The suite includes unit tests for business logic (deal stage calculations, filter matching, permission checks), integration tests for Supabase Edge Functions (verifying rate limits, authentication, and response formats), and end-to-end tests using Playwright that exercise critical user flows like creating a contact, moving a deal through pipeline stages, and sending an email. We wrote about the testing philosophy behind these 375 tests and why we prioritize test coverage even as a solo founder.
The CI pipeline runs on GitHub Actions. Every pull request triggers the full test suite, an ESLint pass, a TypeScript type check, and a build verification. The pipeline blocks merge on any failure. We also run npm audit in CI to catch known vulnerabilities in dependencies. When a critical CVE is published for a package we use, the next CI run flags it and we can patch before deploying. This is not paranoia, it is basic hygiene. Dependency supply-chain attacks are the most common vector for compromising Node.js applications, and automated auditing is the simplest countermeasure.
SSL is table stakes in 2026, but it is still worth verifying. We enforce HTTPS-only via Vercel's automatic SSL provisioning, HSTS headers with a one-year max-age, and a redirect rule that sends all HTTP traffic to HTTPS. The Supabase connection uses SSL by default, but we also verified that our connection string includes sslmode=require so that even a misconfigured client cannot fall back to plaintext.
Backups happen at two levels. Supabase provides automatic daily backups with point-in-time recovery on the Pro plan. We supplement this with a nightly pg_dump that we store in a separate cloud storage bucket. The rationale is simple: if something catastrophic happens to our Supabase project, we want an independent copy that we control. The pg_dump runs as a scheduled GitHub Action, uploads the compressed dump to encrypted S3-compatible storage, and retains 30 days of history. We have tested the restore process twice and documented every step so that a full recovery can happen in under an hour.
You do not need a security team to ship secure software. Everything on this checklist is achievable by one developer over a focused weekend. Supabase gives you RLS, Auth, and Edge Functions out of the box. Sentry's free tier covers error tracking for most early-stage products. GitHub Actions is free for public repos and cheap for private ones. Let's Encrypt and Vercel handle SSL automatically.
The real cost is not money, it is attention. Each item on this list requires you to stop building features and focus on infrastructure. That feels wasteful when you are racing toward launch. But the alternative is launching with API keys in your client bundle, no error tracking, and no test suite, then spending your launch day patching vulnerabilities instead of talking to users. We chose to invest the weekend before launch, and it was the best time we spent in the entire build. If you are preparing for your own Product Hunt launch, start with this checklist. Adapt it to your stack, check every box, and launch with confidence.