Fix.
The production web app penetration testing checklist
A practical technical checklist for founders and engineers to test web applications for critical authorization bugs, data leaks, and authentication flaws.
Before opening a web application to public signups or processing live financial transactions, development teams must test their software the way an attacker would.
Modern full-stack web applications introduce security failure modes that traditional vulnerability scanners often miss, such as broken database permissions, client-exposed credentials, and object-level authorization bypasses.
Here is the six-point penetration testing checklist we run on web applications before production release.
1. Test for broken object-level authorization (BOLA)
Broken object-level authorization is the most common and dangerous vulnerability in modern SaaS platforms. It occurs when an API endpoint returns or modifies a record based on an ID supplied in the request, without verifying whether the authenticated user owns that record.
To test this:
- Create two test accounts: Account A and Account B.
- Under Account A, create a private resource (for example, an invoice, a project, or an uploaded document) and note its unique UUID or ID number.
- Authenticate as Account B. Use your browser developer tools or an API client like Postman to send a
GET,PUT, orDELETErequest for Account A’s resource ID using Account B’s authentication cookies or JWT. - If the server returns Account A’s data with HTTP status 200, your application has a critical BOLA vulnerability.
Every database query and API handler must enforce tenancy filters at the database level:
-- Secure: Always filter by authenticated user id
select * from invoices
where id = target_invoice_id
and user_id = auth.uid();
2. Probe public database tables for missing RLS
If your backend uses a cloud database like Supabase or Firebase, your frontend client communicates directly with database endpoints.
Test your public endpoints using an anonymous session:
- Open an incognito browser window and copy your application’s public Supabase URL and anonymous anon key from the page source.
- Use curl or the Supabase JavaScript library from your browser console to run queries directly against your tables:
// Test query in browser console
const res = await supabase.from('users').select('*');
console.log(res.data);
- If this query returns rows containing emails, phone numbers, or metadata, Row-Level Security is either turned off or configured with an open policy (
using (true)). - Run this check across every table in your database schema. Every table must have RLS explicitly enabled.
3. Scan compiled JavaScript assets for administrative secrets
Developers frequently store third-party tokens in environment files without understanding which variables get compiled into public browser bundles.
Inspect your production build:
- Open your browser developer tools and navigate to the Sources tab.
- Search through compiled
.jsfiles for dangerous patterns:service_role: Supabase administrative keys that bypass RLS entirely.sk_live_: Stripe live secret keys that allow full payment and customer access.Bearer: Hardcoded third-party API keys (OpenAI, Resend, Sendgrid).
- If any secret key appears in the bundle, revoke and rotate the key immediately. Move all operations requiring administrative privileges to serverless edge functions.
4. Test payment webhooks against replay and payload forgery
Attackers frequently attempt to obtain premium features without paying by sending forged webhook events to your billing endpoints.
Verify the resilience of your webhook handler:
- Send a raw POST request with a simulated
checkout.session.completedpayload directly to your webhook URL without a validstripe-signatureheader. - The endpoint must return HTTP status 400 and refuse to process the upgrade.
- Ensure the endpoint checks event idempotency. Send the same verified webhook event twice. The second request must acknowledge receipt without crediting the account a second time.
5. Verify session invalidation upon password reset
When a user changes their password or updates security credentials, active sessions across all devices must be invalidated immediately.
To test:
- Log into your account on two separate browsers (Browser 1 and Browser 2).
- On Browser 1, change your account password.
- Switch to Browser 2 and attempt to perform an action (such as updating profile information or fetching account data).
- If Browser 2 can still make authenticated requests, your session tokens are not being revoked on the server. Configure your authentication provider to rotate refresh tokens upon password modification.
6. Audit CORS configurations and cross-site protections
Cross-Origin Resource Sharing (CORS) policies dictate which external domains can interact with your backend API.
- Verify that your API headers do not return
Access-Control-Allow-Origin: *on endpoints that handle authenticated user credentials. - Ensure authentication cookies declare
SameSite=LaxorSameSite=Strict, along with theSecureandHttpOnlyflags. This blocks cross-site request forgery attacks.
Getting a professional penetration test
Finding vulnerabilities before malicious actors do protects your reputation and prevents costly legal and financial repercussions.
If you are launching a web application and need an independent, thorough audit, review our web application security testing services or request a free external production diagnosis.
More guides on fix
- Hardening Supabase and Node backends for production8 min read
- Migrate a Lovable app from Vite to Next.js and Supabase8 min read
- How to fix infinite recursion in Supabase RLS6 min read
- How to migrate a vibe-coded app to a standalone codebase9 min read
- The vibe coded MVP audit checklist8 min read
- Taking over a codebase someone else built6 min read
- Lovable Cloud to Supabase migration, what it actually involves7 min read
- What it actually costs to fix a vibe-coded app6 min read
- Is your Lovable app ready for real users? Ten checks8 min read
- Supabase RLS policies that actually work7 min read
- Your Supabase service role key is in your frontend. Do this now5 min read
- Check whether your Supabase data is public in five minutes5 min read
- Why your Lovable app shows a blank page after deploy6 min read