Web app pentest: what gets tested, how it runs, how to prepare

A web app pentest is an authorized attack on your live web application that proves, with the request that worked, what a stranger or a signed-in user can actually reach. It runs in phases: scope, discovery, attack, report, then a retest of every fix. On React, Next.js, Supabase and Firebase apps, the main target is access control, which OWASP ranks first in its 2025 Top 10.

The team behind Keybrum's attacksUpdated 13 min read

In short

  • A pentest proves each finding with the request that worked. A vulnerability scan lists what might be wrong. Ask for proof on every finding.
  • On apps where the browser talks to Supabase or Firebase directly, the database policies and security rules are the access control, so they are the main target.
  • Grey box testing with two accounts per role is the sensible default: many access control bugs only show up when user B asks for user A's data.
  • Before the test, write the scope, prove you own every domain, read your hosting providers' testing rules and create test accounts with data you can lose.
  • A finding is closed when the same request fails on a retest, not when the ticket says fixed. Every deploy can reopen it.
On this page

What a web app pentest is, and what it is not

A web application penetration test, or web app pentest, is an attack on your own app, run with your written permission, to find out what someone else can actually do with it: read data that is not theirs, skip a login, call a route meant for admins, reach a secret key. NIST's technical guide to security testing describes the attack phase as verifying suspected weaknesses by attempting to exploit them. So every finding in a good report comes with the request that worked, and you can replay it yourself.

That is the difference with a vulnerability scan. A scanner matches versions and responses against known patterns and returns a list of things that might be wrong. A pentest proves which ones are real, and finds the bugs a scanner cannot see without context: one user reading another user's records, a price changed in the request, a permission that only exists in the interface. The pentest vs vulnerability scan guide compares the two in detail.

It matters most for apps that were built fast. The OWASP Top 10:2025 puts broken access control first and says 100% of the applications tested in its data set had some form of it. In apps generated with AI tools, the pattern is on public record: CVE-2025-48757 was assigned after a researcher found that 170 of 1,645 Lovable apps tested, about one in ten, let unauthenticated visitors read or write data because of missing or weak row level security (RLS). Lovable disputes the CVE, on the grounds that each customer is responsible for protecting their own app's data.

Black box, grey box or white box?

The three names describe how much the tester knows before starting. They change what the test can find, so choose on purpose.

TypeWhat the tester getsFinds wellMisses
Black boxThe address of the app, nothing elseWhat any visitor can reach: keys in the JavaScript, open database tables, exposed files, known CVEsMost of what sits behind the login, unless sign-up is open
Grey boxThe address, test accounts for each role, sometimes API docsAccess control between users and roles, IDOR, privilege escalation, business logicFlaws only visible in the code, such as a dangerous query on a route the interface never calls
White boxEverything above, plus source code, database schema and security rulesInjection paths, secrets in the repository, missing checks on rarely used routes, weak policiesLittle, but it takes longer, and reading code alone does not prove a flaw is reachable on the live app

For an app with user accounts, grey box is the sensible default. Many access control bugs only appear when user B asks for something that belongs to user A, and a black box tester without two accounts cannot see that. If your code sits in a repository you control, as it usually does with apps built in Cursor or Claude Code, adding a white box pass is cheap: reading the RLS policies, the Firebase rules and the API handlers tells the live attacks where to look. The white box, black box and grey box guide shows what each one finds on a generated app.

The phases of a web application penetration test

NIST SP 800-115 splits a penetration test into four phases: planning, discovery, attack and reporting. Vendors rename and subdivide them, but the shape holds. The fifth step below is the one that decides whether anything got safer. The penetration testing methodology guide compares the standards behind these phases.

  1. 1

    Scope and rules of engagement

    List every domain, subdomain, API host and backend project in scope, and what is out, such as your payment or email provider. Agree on the test window, the accounts to use, what the tester may write or delete, and who to call if something breaks. In NIST's model no testing happens in this phase: it ends with documented approval.

  2. 2

    Discovery

    The tester maps the attack surface: pages, JavaScript bundles, API routes, Server Actions, the backend the browser talks to, the keys and table names shipped in the bundle, source maps, forgotten subdomains. Then comes vulnerability analysis: comparing what was found, framework versions included, with known weaknesses.

  3. 3

    Attack

    Each suspicion is tried for real: as a stranger, as user A, then as user B asking for A's data. A careful tester proves impact with the least harm possible: one row read rather than the whole table, writes rolled back, no denial of service.

  4. 4

    Report

    Each finding gets a severity, the affected endpoint, the exact request and response that proved it, the impact in plain words and the fix. NIST notes that reporting runs alongside the other phases. In practice, ask to hear about a critical finding the day it is found, not in a PDF at the end.

  5. 5

    Fix and retest

    You fix, then the same request and its variants are replayed. Close the finding only once the attack fails.

What gets tested on a modern web app

The categories are old; where they live has moved. In a React or Next.js app on Supabase or Firebase, the browser often talks to the database directly, so the database policies are the access control. An external pentest covers the rest of what you expose: domains, storage and the server behind the app.

  • Authentication. Sign-in with no rate limit, reset links returned in the API response, sign-up that reveals which emails exist, session cookies without HttpOnly or Secure.
  • Access control. User B reading or changing A's record by swapping an id, known as an IDOR; a role field accepted in a profile update; Supabase policies or Firebase rules that only check that someone is signed in.
  • Data exposure. API responses that return whole rows, with emails and internal flags, when the screen shows a name; public storage buckets holding private files.
  • Injection. SQL built from strings; user content rendered as raw HTML; a server that fetches any URL it is given, called server-side request forgery (SSRF).
  • Misconfiguration. A served .env file or .git folder, public source maps, debug pages, permissive CORS, missing security headers. See the exposed .env guide.
  • Secrets. Keys that reached the bundle through a NEXT_PUBLIC_ or VITE_ variable: OpenAI, Stripe secret, Supabase service role. Next.js and Vite copy those variables into the browser code at build time.
  • Known CVEs. For example the Next.js middleware bypass, CVE-2025-29927, or React2Shell, CVE-2025-55182, which CISA lists as exploited in the wild. A good test proves the flaw by its behaviour, not from a version number.
  • AI routes and webhooks. AI endpoints that answer anyone and spend your credits (unprotected AI endpoints), payment webhooks that skip the signature check (Stripe webhook security).

OWASP Top 10 2025, mapped to Next.js, Supabase and Firebase

Most reports map findings to the OWASP Top 10. The 2025 edition reordered it, added Mishandling of Exceptional Conditions, widened Vulnerable and Outdated Components into Software Supply Chain Failures, and folded SSRF into Broken Access Control. Here is what each category looks like in a modern app, and how a tester checks it.

OWASP Top 10:2025On a modern stackHow it is checked
A01 Broken Access ControlA table with RLS off or a policy using (true); Firestore rules open to any signed-in user; /api/orders/123 answering any account; a Server Action with no auth check; an image optimizer that fetches internal URLsTwo accounts: B asks for A's ids. The public key tried on every table, function and bucket
A02 Security Misconfiguration.env or source maps served, debug pages on, CORS reflecting any origin, no CSP or HSTS headerKnown paths compared with a 404 baseline, response headers read
A03 Software Supply Chain FailuresAn outdated Next.js or React with a known CVE, a compromised npm packageLockfile review in white box, the CVE proved by behaviour in black box
A04 Cryptographic FailuresA guessable JWT or session secret on a self-hosted backend, pages served over plain HTTPToken signatures checked against known weak secrets, HTTPS and HSTS checked
A05 InjectionString-built SQL in an API route or database function, user content rendered as raw HTMLParameters probed for database errors that only raw SQL produces, output encoding checked
A06 Insecure DesignPrices, credits or plan limits enforced only in the browser, an AI route with no quotaRequests replayed with changed values or past the limit
A07 Authentication FailuresNo throttling on sign-in, reset tokens in API responses, account enumeration, unsigned tokens acceptedA few bounded failed sign-ins, reset and sign-up responses read
A08 Software or Data Integrity FailuresA Stripe or Clerk webhook handler that does not verify the signatureAn unsigned test event sent to your own endpoint must be refused
A09 Security Logging and Alerting FailuresNo trace of who read what, no alert on a burst of denied requestsAsked afterwards: did the test show up in your logs?
A10 Mishandling of Exceptional ConditionsStack traces or SQL errors in API responses, auth that fails open when a service times outMalformed input sent, error responses read
Category names from the OWASP Top 10:2025. The examples and checks are ours, for apps on these stacks.

Run the two-account test on your own app

You do not need a tester to try the most common finding. Create two accounts in your own app, sign in as each one in a separate browser profile, and copy each access token from the Authorization header in the developer tools Network tab. Then ask for one of A's records as B.

bash
# Run against your own app only, with two test accounts you created.
# 1. As user A, read one of A's records and note its id.
curl -s "https://your-app.example/api/orders/A_ORDER_ID" \
  -H "Authorization: Bearer $TOKEN_A"

# 2. As user B, ask for the same id.
curl -s -w "\nHTTP %{http_code}\n" "https://your-app.example/api/orders/A_ORDER_ID" \
  -H "Authorization: Bearer $TOKEN_B"
If step 2 returns A's data, any signed-in user can read any record by its id. Expect a 403 or a 404. Repeat with PATCH and DELETE on a test record.
bash
# Supabase: the same check through the REST API of your own project.
# Use the table and id of one of A's rows, and B's access token.
curl -s "https://YOUR_PROJECT_REF.supabase.co/rest/v1/orders?id=eq.A_ORDER_ID&select=*" \
  -H "apikey: $SUPABASE_PUBLISHABLE_KEY" \
  -H "Authorization: Bearer $TOKEN_B"
Use the publishable (or legacy anon) key your app already ships. With working RLS, B gets an empty array [], not an error. If A's row comes back, the policy does not check ownership.

On Supabase, the Supabase RLS guide shows the policies that stop it. On Firebase, paste your rules into the Firebase rules checker. On Next.js, remember that a Server Action is reachable by a direct POST request even when the page that renders it checks the session: the Next.js docs say to verify authentication and authorization inside each action.

How to prepare your app for a pentest

A pentest is only as good as the access it gets. Sort these out before day one.

  • Write the scope. Every domain, subdomain and API host, plus the backend projects behind them: Supabase project ref, Firebase project ID. Name what is out of scope too, such as your payment or email provider.
  • Prove you own it. Expect to prove you control each domain, for example with a DNS TXT record or a file on the site, and to sign a written authorization. Be wary of anyone who will test without one.
  • Read your providers' rules. Supabase lets customers test their own project without prior approval, but forbids denial of service, request flooding and cross-tenant attacks. Vercel allows non-volumetric tests on Pro and Enterprise plans without notice, not on Hobby; volumetric tests need Enterprise and advance notice, and you pay the usage the test generates.
  • Create test accounts. Two per role: two regular users, two admins if you have an admin area, in two different teams if your app is multi-tenant. Give each one some data, so there is something to protect.
  • Pick the environment. Production shows the real configuration. Staging is safer for write tests, but only tells you about production if it runs the same build, the same policies or rules and the same environment variables.
  • Back up and seed. Take a database backup and use test records you can afford to lose.
  • Let the tester through. Tell your team the dates. To test the app rather than the firewall, allowlist the tester's IP addresses: Vercel suggests setting them to Bypass in the project firewall.
  • Share what helps. The list of roles, the tables holding personal data, an OpenAPI file if you have one, and repository access for a white box test.

How to read a pentest report

A report is useful when each finding can be replayed and fixed without a meeting. Check that every finding has these four parts.

  • The proof: the exact request, the identity that sent it (anonymous, user A, user B) and the response that showed the problem.
  • The impact in your terms: which data, how many rows or users, read or write.
  • A severity, usually a CVSS score. On the CVSS scale, 0.1 to 3.9 is low, 4.0 to 6.9 medium, 7.0 to 8.9 high and 9.0 to 10.0 critical.
  • The fix, at the right layer: a database policy, a server-side check, a header, an upgrade. Hiding a button is not a fix.

Read severity with your own data in mind. An IDOR rated medium on a generic scale can be the worst finding in the report if the records are invoices or medical notes. Watch for three kinds of padding: findings with no proof, pages of informational notes about headers, and CVEs claimed from a version string alone. The sample pentest report shows what a finding with proof looks like.

Retesting: when is a finding actually fixed?

Replay the exact request that proved the finding, with the same identity. If it now fails, check the variants before you close it:

  • The same bug on sibling routes: fixed on /api/orders/:id, still open on /api/invoices/:id.
  • Other methods on the same route: reads blocked, updates still allowed.
  • Other doors to the same data: a table locked by RLS but still readable through a view or a database function.
  • Leftovers: a leaked key removed from the code but never rotated, a private page still served from a cache.

Then keep retesting. A fix lives in code that keeps changing, and a coding agent asked for a new feature can rewrite the route you just fixed. A pentest from last spring says little about the app you shipped this week, which is why we attack again after every fix and on every deploy.

Manual pentest or automated attacks?

Both have a place. A human tester is the best way to find flaws specific to your business: a refund that can be claimed twice, an invite link that grants the wrong role, a chain of small bugs. Automated attacks cover the known classes every time the code changes, with the same proof. For a small team, a workable order is automated attacks from the first deploy, then a manual test before a big launch or an enterprise deal. See automated penetration testing, penetration testing for startups and penetration testing cost.

Keybrum is the automated side, built for apps made with AI. It runs more than 100 attacks as a stranger and as two signed-in users: Supabase tables, functions and storage, Firebase rules, IDOR across accounts, secrets in the bundle, unprotected AI routes, unsigned Stripe webhooks, exposed .env files, and Next.js and React CVEs proved by behaviour, never with remote code execution or denial-of-service payloads. It only attacks apps whose owner proved ownership, and your coding agent reads each finding and its fix through MCP.

Questions

What is the difference between a website pentest and a vulnerability scan?

A vulnerability scan compares your site with known patterns and lists what might be wrong. A pentest attacks the live app and proves which problems are real, including access control and logic flaws that a scanner cannot judge without context. See pentest vs vulnerability scan.

Can I pentest a website I do not own?

No. Without written permission from the owner, a pentest is an attack. Hosting providers enforce this too: Supabase treats testing outside its policy as testing against Supabase itself and may flag it as abuse. Test your own apps, or get written authorization first.

Is there a web app penetration testing checklist?

The OWASP Web Security Testing Guide is the reference list of tests; its latest stable release is 4.2, and version 5.0 is in development. The OWASP ASVS 5.0.0, published in May 2025, lists the requirements an app should meet. The preparation checklist and the OWASP table on this page are the short version for a modern stack.

How often should you pentest a web application?

Run automated attacks on every deploy, because each deploy can reopen a hole you fixed. Add a manual pentest before major launches, after large changes to authentication or permissions, and when a customer or an audit asks for one. The Next.js data security guide also recommends penetration testing or vulnerability scanning regularly, or in step with your development lifecycle.

Should a pentest run on production or staging?

Production shows what strangers really reach: the real headers, keys and rules. Staging is safer for tests that write data, but only tells you about production if it runs the same build, policies and environment variables. One workable split is read-only attacks on production and write tests on staging.

Sources

  1. 1.OWASP Top 10:2025, Introduction, OWASP
  2. 2.A01:2025 Broken Access Control, OWASP
  3. 3.SP 800-115, Technical Guide to Information Security Testing and Assessment, NIST
  4. 4.How to think about data security in Next.js, Next.js Docs
  5. 5.How to use environment variables in Next.js, Next.js Docs
  6. 6.Env variables and modes, Vite Docs
  7. 7.Penetration testing and security testing policy, Supabase Docs
  8. 8.Penetration testing on Vercel, Vercel
  9. 9.Row Level Security, Supabase Docs
  10. 10.Understanding API keys, Supabase Docs
  11. 11.Avoid insecure rules, Firebase Docs
  12. 12.Web Security Testing Guide, OWASP
  13. 13.Application Security Verification Standard, OWASP
  14. 14.CVSS v4.0 Specification Document, FIRST
  15. 15.CVE-2025-29927, NIST National Vulnerability Database
  16. 16.CVE-2025-55182, NIST National Vulnerability Database
  17. 17.CVE-2025-48757, NIST National Vulnerability Database

Your app is public. So are its mistakes.

Check what any attacker can already see in it, free, in ten seconds. The full attack, with proof and fixes: €49 once, or €29 a month on a plan.

or continue with Google or GitHub