A SaaS security audit is not a single test. It is a structured pass through every layer where data can leak or access can be bypassed: authentication, authorization, secrets, the API surface, the database, dependencies, and logging. This checklist covers each layer in the order auditors typically work through it, with the issues AI-built apps fail most often called out explicitly.
Before the audit: the automated pass
Before any manual review, run automated scanning to clear the obvious issues. An auditor who finds hardcoded API keys or JWT decode-without-verify on day one will spend audit time on basic cleanup instead of the nuanced issues that actually need human judgment. Scan first, fix first.
- Check for exposed secrets — scan your public URL for API keys, tokens, and credentials in client-side bundles
- Run a static secrets scan across your full repo and git history, including migration files and seed scripts
- Check Supabase RLS if your app is Supabase-backed — confirm Row Level Security is on before the auditor tests it
- Check HTTP security headers — X-Frame-Options, CSP, HSTS
Scan your own app for issues like these
Paste your live URL. We check what your app serves publicly for exposed keys and misconfigurations. No account, no install.
1. Authentication
Authentication failures are a blocker in any audit. Work through these before anything else:
- JWT handling. Every route that reads a JWT must use
jwt.verify()with a pinned algorithm, notjwt.decode(). See JWT decode vs verify. - Fallback secrets. No
process.env.SECRET || 'dev-default'patterns. If a required secret is missing, the app should crash loudly, not silently use a predictable value. - Password hashing. bcrypt or Argon2 with appropriate work factor. Never MD5, SHA-1, or plain SHA-256 for passwords.
- Session fixation. Regenerate the session ID on login.
- Token storage. JWTs stored in localStorage are readable by any XSS. HttpOnly cookies prevent this.
2. Authorization (the most common failure point)
Authorization is where AI-built apps fail most often. There are two separate checks to make:
- Is there an auth check at all? Every API route that reads or writes user data must verify the caller is authenticated before doing anything else. AI tools generate the route handler that satisfies the feature, not the security wrapper around it.
- Does the auth check include ownership (BOLA)? Confirming a user is logged in is not enough. Every route that fetches a record by ID must also confirm that record belongs to the requesting user. An attacker who is logged in as user A should not be able to read user B’s invoices, messages, or files. See what is BOLA.
// VULNERABLE: checks auth but not ownership
app.get('/api/documents/:id', authenticate, async (req, res) => {
const doc = await db.document.findById(req.params.id); // returns anyone's doc
res.json(doc);
});
// CORRECT: checks both
app.get('/api/documents/:id', authenticate, async (req, res) => {
const doc = await db.document.findOne({
_id: req.params.id,
userId: req.user.id, // must belong to this user
});
if (!doc) return res.status(404).json({ error: 'Not found' });
res.json(doc);
});3. Secrets and credentials
- No API keys, database connection strings, or JWT secrets in source files or git history
- No
NEXT_PUBLIC_prefix on server-only secrets. See NEXT_PUBLIC_ env variable leak. - Every secret read via
process.envwith a startup check — fail loudly if missing service_rolekey (Supabase) or equivalent admin credentials never in client code- Secrets rotated if they have ever appeared in a commit or error message
4. API security
- CORS. No
Access-Control-Allow-Origin: *on endpoints that read authenticated data. See CORS wildcard fix. - Rate limiting. Auth endpoints (login, password reset, OTP) rate-limited to prevent brute force and enumeration.
- Input validation. All user-supplied strings validated and sanitized before use in queries, commands, or HTML output.
- SQL injection. Parameterized queries or ORM methods everywhere. No string-concatenated queries. See SQL injection fix.
- Mass assignment. Every update route whitelists acceptable fields — never passes
req.bodydirectly to an ORM update.
5. Database access
- Row Level Security enabled on every table (Supabase, Postgres with RLS)
- Anonymous read tested — a request with no auth should return nothing from user tables
- Database user has minimum required permissions — no app-level access to DDL operations
- No direct database access from client-side code
6. Dependencies
npm auditor equivalent run regularly — HIGH and CRITICAL advisories triaged- Supply chain: packages from known publishers, no typosquatted dependencies
- Lockfile committed and verified in CI
7. HTTP security headers
Strict-Transport-Security(HSTS) — force HTTPSX-Frame-Options: DENYorSAMEORIGIN— prevent clickjackingX-Content-Type-Options: nosniffContent-Security-Policy— restrict script sources- Check yours: free security headers checker
8. Logging
- Auth events logged: successful login, failed login, password reset, token issuance
- No sensitive values in logs — no passwords, tokens, or full API keys
- No PII in URL parameters or query strings — these appear in access logs and browser history
The three places AI-built SaaS apps fail every time
If your SaaS was built primarily with AI coding tools (Cursor, Lovable, Bolt, Claude Code, Windsurf), these three categories have the highest probability of having unfixed issues:
- Hardcoded secrets. The tool inlined a credential to get the integration working and it never got moved to an environment variable. Check git history, not just the current branch.
- Missing ownership checks (BOLA). The route checks authentication but not that the requested record belongs to the caller. Every route that fetches by ID is a candidate.
- JWT decode without verify. The auth middleware reads the JWT payload using
decode()— no signature check, anyone can forge a token. See JWT decode vs verify.
The full checklist for AI-built apps is in secure vibe coding. Prbl’s scanner catches all three automatically — run a free scan before your audit engagement starts.
Common questions
What does a SaaS security audit check?
A SaaS security audit covers authentication and session management, authorization and access control (especially BOLA/IDOR), secrets and credential exposure, API security, database access controls, input validation, dependency vulnerabilities, and logging. For AI-built apps, secrets management and authorization are checked first because they are the highest-frequency failure categories.
How do I prepare my SaaS app for a security audit?
Work through the checklist in order: first rotate any secret that appears in source code or git history, then verify every API route has server-side auth, then check JWT handling uses verify() not decode(), then confirm database RLS is on. Run a scanner against your repo before the audit to find and fix the obvious issues first — auditors will find them, and finding them yourself first saves time.
How long does a SaaS security audit take?
A focused penetration test on a SaaS app typically takes 3–10 business days depending on scope. A lighter security review (architecture, code, automated scan) can turn around in 1–3 days. The biggest time variable is the size of the API surface and whether the codebase has prior automated scanning history.
What is BOLA in a SaaS security audit?
BOLA (Broken Object Level Authorization) means an API endpoint checks that the caller is authenticated but does not verify that they own the specific record they are requesting. For example, GET /api/invoices/:id returns any invoice to any logged-in user, not just the invoice owner. BOLA is the single most common finding in SaaS security audits and is extremely common in AI-generated backend code.
Do I need a security audit before launching a SaaS?
For a B2C product with no sensitive data, a self-audit using this checklist and an automated scanner is a reasonable starting point. For a B2B product, any product handling payment data or PII, or any product pursuing SOC 2, a professional pen test before or shortly after launch is strongly recommended. Enterprise customers will ask for it.