White box pentest: what it finds in an AI-built app, and what it cannot prove

A white box pentest gives the tester your source code, database migrations, security rules and test accounts before the attack starts; a black box test gets only the app's address, and a grey box test gets accounts but no code. White box finds flaws written in the code, like a Supabase policy using (true). It cannot prove alone what the live app exposes, so pair it with an attack on the running app.

The team behind Keybrum's attacksUpdated 13 min read

In short

  • White, grey and black box describe how much the tester knows before starting: your code and configuration, test accounts only, or just the app's address.
  • In an app built with AI, access control is written in files a reviewer can read: Supabase migrations, Firebase rules, API routes and Server Actions.
  • Code review cannot prove reachability. Dashboard edits do not reach the repository on their own, and OWASP notes that the code deployed might differ from the code reviewed.
  • Grey box, with two test accounts per role, is the usual choice for a SaaS: many access control bugs only show when user B asks for user A's data.
  • The strongest setup reads the code to find candidates, attacks the live app to prove them, and keeps unproven candidates as leads, not findings.
On this page

What a white box pentest is

A white box pentest is a penetration test where the tester gets the inside view first: source code, database migrations, security rules, configuration and usually test accounts. NIST defines white box testing as assuming explicit and substantial knowledge of the internal structure and implementation of what is tested. The tester reads the code to decide what to attack, then attacks the running app and proves what worked.

Black, grey and white box describe how much the tester knows on day one:

  • Black box. The tester gets the app's address and nothing else, like a stranger. It shows what anyone can reach, but much of the paid time goes into discovery.
  • Grey box (or gray box). The tester gets test accounts for each role and sometimes API docs, what NIST calls some knowledge of the internals. For an app with user accounts, it is the sensible default.
  • White box (also crystal box or clear box). The tester also gets the code, schema and rules. It finds flaws that are hard to reach from outside, at the price of more specialist time.
Black boxGrey boxWhite box
Knowledge givenThe app's addressThe address plus test accounts for each role, sometimes API docsAll of that plus source code, migrations, rules and configuration
Finds bestKeys in the JavaScript, open tables, exposed files, framework CVEsAccess control between users and roles, IDOR, business logicWeak policies, missing checks on rarely used routes, injection, secrets in the repository
MissesMost of what sits behind the login, and routes the interface never callsCode paths the interface never triggersConfiguration outside the repository, unless the live app is attacked too
Time and costMuch of the time goes into discoveryUsually the best value for web, API and mobile testsMore specialist time
Time and cost notes from Blaze Information Security's 2026 pricing guide. The rest of the table is ours, for apps built on Supabase, Firebase and Next.js.

For scale, Blaze prices a manual test of a simple SaaS app at $4,999 to $8,999 for 3 to 7 testing days, and a complex one at $20,000 to $30,000 or more for two to three weeks or longer. The penetration testing cost guide explains day rates, and the web app pentest guide covers a full test.

What white box review looks like on an app built with AI

Apps generated with Lovable, Bolt, v0, Cursor or Claude Code keep most of their access control in a few files. On Supabase or Firebase the browser often talks to the database directly, so the policies or rules are the access control. A reviewer reads these first:

WhereWhat the reviewer looks for
supabase/migrations/*.sqlenable row level security after every create table; using (true) on private data; authenticated policies that never compare a column to auth.uid(); security definer functions and views that skip RLS. The Supabase RLS checker reviews pasted policies
firestore.rules, storage.rules, database.rules.jsonRules that only check request.auth != null or say if true, and write rules that never check which fields change. The Firebase rules checker flags these
API routes, Server Actions, Edge FunctionsA session check inside each one, the user id taken from the session, an ownership check before every read or update
Environment handlingNEXT_PUBLIC_ or VITE_ variables, which reach the browser, holding a service role key, Stripe secret or OpenAI key; a committed .env file
DependenciesLockfile versions of Next.js and React against published CVEs; the Next.js and React CVE checker does it in one paste
Webhooks and AI routesStripe signature verification before any write; a session, a per-user limit and a spend cap on routes that call a model
Start with the database policies: when the browser talks to the database directly, they decide who reads what.

A coding agent can do a first pass. Cloudflare's open-source security-audit skill runs it in six phases and gives every candidate to a fresh verifier that tries to disprove it, ending each as confirmed, needs_validation or rejected. Keybrum's code audit includes it; the AI pentest guide explains why that independent check matters.

Three bugs: what the code shows, what the live app proves

Three patterns from generated code: what a reviewer spots in the repository, and what only a request to the running app settles.

1. A Supabase policy using (true)

sql
-- supabase/migrations/20260310120000_profiles.sql
create table public.profiles (
  id uuid primary key references auth.users on delete cascade,
  full_name text,
  email text,
  stripe_customer_id text
);

alter table public.profiles enable row level security;

create policy "Profiles are viewable"
  on public.profiles for select
  using (true);
RLS is on, so the table looks protected in the dashboard.

What white box review spots. The policy lets every row through. With no to clause, a Postgres policy applies to every role, anon included. Supabase says a using (true) read policy for anon gives every unauthenticated visitor every row the role can reach through its grants, so use it only for public data. Emails and Stripe customer ids are not.

What a black box test proves. Whether a stranger gets those rows today, which the migration cannot show: whether anon holds a select grant in production, and whether someone has changed the policy in the dashboard since. Supabase warns that schema changes made on the remote database through the SQL editor or Table Editor bypass the migration history. The proof is one row read with your public key while signed out, against your own project.

The fix limits the read to the owner, in a new migration:

sql
drop policy "Profiles are viewable" on public.profiles;

create policy "Users read their own profile"
  on public.profiles for select
  to authenticated
  using ((select auth.uid()) = id);
Only the signed-in owner of the row can read it.

Put any field that must be public in a separate table, or in a view created with security_invoker = true so it obeys RLS. The Supabase RLS guide covers the common cases.

2. A Firebase rule that only checks request.auth != null

js
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /users/{userId} {
      allow read, write: if request.auth != null;
    }
  }
}
firestore.rules: any signed-in user, any document.

What white box review spots. Any signed-in user can read and overwrite every document in users. Firebase's guide to insecure rules names this pattern: if a rule includes auth != null, confirm that you want any logged-in user to have access. The fix: allow read, write: if request.auth != null && request.auth.uid == userId;.

What a black box test proves. Whether a stranger can become "any logged-in user", which depends on the sign-in methods enabled in the Firebase console, not on the rules file. And whether production runs firestore.rules at all: a CLI deploy overwrites rules edited in the console, so a console-only fix disappears at the next deploy. The proof is account B reading account A's document. The Firebase security rules guide has owner-only patterns.

3. A server route that trusts a user id from the body

ts
// app/api/account/route.ts
import { db } from "@/lib/db";

export async function POST(req: Request) {
  const { userId, displayName, plan } = await req.json();
  await db.user.update({
    where: { id: userId },
    data: { displayName, plan },
  });
  return Response.json({ ok: true });
}
The route updates whichever user the request names.

What white box review spots. The route reads no session, updates whichever user id the request names, and accepts a plan field the interface never sends: an IDOR and a free upgrade in one handler. The Next.js data security guide says the same of Server Actions: a page-level check does not cover them, a direct POST reaches them, and each must check that the caller may act on that specific record.

What a black box test proves. Whether the route is reachable in production and what it changes. A firewall rule or a missing deploy can block it; a second copy under another path can make it worse. The proof is account B changing account A's record, then reading it back.

ts
// app/api/account/route.ts
import { auth } from "@/lib/auth";
import { db } from "@/lib/db";

export async function POST(req: Request) {
  const session = await auth();
  if (!session?.user) {
    return Response.json({ error: "Unauthorized" }, { status: 401 });
  }
  const { displayName } = await req.json();
  await db.user.update({
    where: { id: session.user.id }, // from the session, never from the body
    data: { displayName },          // plan changes go through billing only
  });
  return Response.json({ ok: true });
}
The user id comes from the session, and the client can only change the fields it is allowed to.

What a black box test proves that code review cannot

OWASP's Web Security Testing Guide calls source code analysis the technique of choice for technical testing, strong on access control and business logic, and free of black box guesswork. Among its weaknesses: it cannot easily detect runtime errors, and the source code actually deployed might differ from the one being analyzed. On an app built with AI, that gap has three parts:

  • Reachability. A dangerous function matters only if a request can reach it. Code review flags dead routes with the same confidence as live ones.
  • Real exposure. The code says a policy is broad. The live app says which columns come back, to whom, and whether the table holds ten test rows or every customer.
  • Configuration outside the repository. Policies added in the SQL editor, rules edited in the Firebase console, a bucket switched to public, open sign-ups, hosting variables, a deployed build that is not the commit you reviewed. None of it shows in a diff.

To check whether production still matches your migrations, run this in the SQL editor of your own Supabase project:

sql
-- Tables in the public schema, and whether RLS is on
select tablename, rowsecurity
from pg_tables
where schemaname = 'public';

-- Every live policy, with its roles and conditions
select tablename, policyname, roles, cmd, qual, with_check
from pg_policies
where schemaname = 'public'
order by tablename;
A policy here that no migration creates was added by hand, and the next reset or new environment will not have it.

The reverse holds too. OWASP notes that dynamic testing struggles to cover a whole code base, especially behind nested conditions. A black box test that finds nothing has only shown that its own attacks failed.

Grey box: test accounts and partial knowledge

Grey box sits between the two: the address plus accounts, sometimes API docs or a list of roles, but no code. Blaze calls it usually the best value for web, API and mobile tests, because testers spend their days on real risk instead of discovery.

The accounts are what matter. One user reading another user's data only shows when user B asks for what belongs to user A. Give the tester two regular users, two admins if you have an admin area, and two teams if your app is multi-tenant, each with some data.

Grey box misses what the interface never exercises: an old API route still deployed, a database function no screen calls, a field the server accepts but the form never sends. A white box pass closes that gap.

Which one to choose for your situation

Your situationWhat to buyWhy
A customer's security questionnaire asks for your latest pentestGrey box by a named firmBuyers usually expect a human report; ask which format they accept. See security questionnaire answers
An audit or a framework asks for a testWhat the auditor accepts, often grey box, with white box on the most sensitive codeThe scope and report matter more than the box. See the PCI DSS penetration testing guide
First launch, small budgetThe free check, automated attacks on every deploy, a white box pass by your coding agentCovers the common leaks cheaply until a deal or an audit needs a manual test
A release that changed sign-in, roles or billingWhite box review of the diff, then a live retest of the routes it touchedThe diff tells you where to look
You want to know what strangers seeBlack boxThe outside view; see external penetration testing
An AI agent wrote most of the codeWhite box plus live proofThe code shows where a bad pattern repeats; the live attack shows which copies are reachable

Combining a code audit with live proof

The two fail in opposite directions. Code review alone gives false positives: using (true) on a table of public blog posts is intended, and a dangerous route may be unreachable. Black box alone leaves blind spots: a failed attack may have lacked the right account, the right role or the route's name. Together:

  1. 1

    Read the code for candidates

    Migrations, rules, routes, environment handling, the lockfile. Each suspicion becomes a candidate with a file and a line.

  2. 2

    Attack the live app for each candidate

    Signed out, as user A, then as user B asking for A's data. A candidate becomes a finding only with the request and response that proved it.

  3. 3

    Keep the unproven ones as leads

    The route may be unreachable, or the test lacked an account. Note why and retry; Cloudflare's skill has a needs_validation verdict for this.

  4. 4

    Trace live findings back to the code

    When an attack finds something the code does not explain, such as a dashboard policy, write the fix into a migration or rules file.

  5. 5

    Retest after the fix, and on every deploy

    Replay the same request; the finding closes only when it fails. An agent building a new feature can rewrite the route you just fixed.

We built Keybrum on that split. Your coding agent runs the code audit, Cloudflare's skill included, handed over through Keybrum's MCP server. Keybrum's attack library then tries the findings on the live app, signed out and as a second signed-in user. Your agent reads each fix through MCP, and Keybrum attacks again after the fix and, on plans, on every deploy. It only attacks apps whose owner proved ownership. The automated penetration testing guide covers what still needs a human, and the methodology guide the phases of a test.

What to hand a tester for each type

What to hand overBlack boxGrey boxWhite box
Written authorization and scope: domains, API hosts, Supabase project ref or Firebase project ID, exclusionsYesYesYes
Test accounts, two per role, with data in themNo, or only what open sign-up givesYesYes
The list of roles and the tables or collections holding personal dataNoYesYes
API documentation or an OpenAPI fileNoIf you have oneYes
Read-only access to the repository, migrations, rules files and functions includedNoNoYes
The production policies and rules as they are today, such as the output of the queries aboveNoNoYes, since dashboard edits do not reach the repository on their own
The commit deployed to production and where each environment variable is setNoNoYes: names and locations, never the values

Whichever box you buy, judge the report, ours included, by one question: can you replay each finding yourself?

Questions

What is the difference between black box and white box penetration testing?

How much the tester knows before starting. In a black box pentest the tester gets only your app's address, like a stranger; in a white box pentest they also get the source code, schema, rules and configuration. Black box shows what an outsider can reach, and white box finds flaws written in the code, including on routes the interface never calls.

What is a grey box pentest?

A test where the tester gets partial knowledge, usually test accounts for each role and sometimes API documentation, but not the code. It is the common choice for SaaS apps, because access control bugs between users only show with two accounts. Blaze calls it usually the best value for web, API and mobile tests.

Is a whitebox pentest better than a black box pentest?

It finds flaws that are hard to reach from outside, because the tester reads where they are instead of guessing, though it takes more specialist time. It does not prove on its own what the live app exposes, since configuration outside the repository and the deployed build can differ from the code. The strongest setup reads the code, then proves each finding on the running app.

How long does a white box pentest take?

For a manual test of one web app, Blaze sizes a simple SaaS app at 3 to 7 testing days and a complex one at two to three weeks or more, and says white box takes more specialist time. Automated offers are faster: Intruder sells an AI-powered white box test of a web app, connected to your repository, with a report the same day.

Is a white box pentest the same as a code review?

No. A code review, or a static analysis (SAST) tool, reads the code and lists possible flaws. A white box pentest uses the code to decide what to attack, then attacks the live app and proves each finding with a request. A report with no request behind its findings is a code review, whatever it is called.

Do I have to share my secrets for a white box test?

No. Share read-only repository access, the names of your environment variables and where each one is set, and test accounts. If a test needs a real key, create one for the engagement and revoke it when the test ends.

Sources

  1. 1.White box testing (comprehensive testing), NIST CSRC Glossary
  2. 2.Gray box testing (focused testing), NIST CSRC Glossary
  3. 3.Web Security Testing Guide v4.2: Introduction (source code review, penetration testing), OWASP
  4. 4.Row Level Security, Supabase Docs
  5. 5.Database migrations, Supabase Docs
  6. 6.CREATE POLICY, PostgreSQL documentation
  7. 7.Avoid insecure rules, Firebase Docs
  8. 8.Manage and deploy Firebase Security Rules, Firebase Docs
  9. 9.How to think about data security in Next.js, Next.js Docs
  10. 10.security-audit skill, Cloudflare
  11. 11.How much does penetration testing cost?, Blaze Information Security
  12. 12.AI pentesting, Intruder

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