Audit a vibe-coded or AI-generated SaaS for security and payment failures before it ships, focused on the Next.js + Supabase + Stripe stack. Use this whenever the user is about to deploy, launch, or "ship" a web app that handles authentication, user data, or payments. Trigger on phrases like "is my app secure", "can someone read other users' data", "is my Stripe integration safe", "will someone get a free subscription", "review my SaaS before launch", "I'm going to production", or any review ...
Scanned 9/6/2026
Install to Claude Code
npx -y skills add Comoco235/saas-preflight --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of saas-preflight?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/comoco235-saas-preflight)More formats (shields.io, HTML) on the badges page.
---
name: saas-preflight
description: >-
Audit a vibe-coded or AI-generated SaaS for security and payment failures
before it ships, focused on the Next.js + Supabase + Stripe stack. Use this
whenever the user is about to deploy, launch, or "ship" a web app that handles
authentication, user data, or payments. Trigger on phrases like "is my app
secure", "can someone read other users' data", "is my Stripe integration
safe", "will someone get a free subscription", "review my SaaS before launch",
"I'm going to production", or any review of API routes, server actions,
Supabase RLS policies, Stripe webhooks, or middleware in a SaaS, even if the
user never says the word "audit". Prefer this skill over an ad-hoc code read
whenever real users or real money are about to touch the code.
---
# saas-preflight
A pre-ship security and payment audit for SaaS built fast with AI, on the
Next.js + Supabase + Stripe stack. It finds the failure modes that AI-generated
code ships by default: a stranger reading another user's data, a stranger
getting a paid plan for free, a webhook that silently fails so subscriptions
never activate, a middleware that fails open.
This skill is **defensive only**. It detects weaknesses in the user's own
codebase so they can be fixed. It never writes exploit code, never produces an
attack payload, and never targets a system the user does not own.
## The 7 categories
Every finding carries a rule id of the form `CATEGORY-SUBJECT-NNN`, and every
rule belongs to one category. They are the spine of the audit. Work through all
of them; do not stop at the first scary thing.
1. **`AUTH`**: Is every protected route, server action, and data query actually
checking who the caller is? Covers the service_role key, middleware that
fails open, `getSession` used for trust, CSRF on cookie-authenticated route
handlers, and open redirects after auth.
2. **`DATA`**: Can a user reach rows, files, or columns that are not theirs?
Covers Supabase RLS coverage, queries with no owner filter, mass assignment,
and Storage buckets.
3. **`BILLING`**: Can money be charged without access granted, or access granted
without money? Covers Stripe webhook signature verification, idempotency
(`event.id` dedupe), and entitlement written from a client-supplied redirect.
4. **`INPUT`**: Is untrusted input validated and bounded before it hits the
database, the filesystem, an outbound fetch (SSRF), or the DOM?
5. **`CONFIG`**: Do secrets, keys, and origins differ between environments in a
way that breaks prod or leaks keys to the client?
6. **`ABUSE`**: Can an anonymous user run up the bill (LLM calls, emails,
storage) or exhaust quotas through races?
7. **`TENANCY` (multi-tenant only)**: Can one tenant reach another tenant's data
or session? Covers session cookie scope across subdomains, tenant context
spoofing, subdomain and domain creation validation, and domain
deprovisioning on downgrade. **Skipped automatically for single-tenant apps**,
which is most of them.
Degraded-mode behaviour (what happens when Stripe or Supabase is slow or down)
has no rules of its own yet; it is checked by reading, under whichever category
the failing call belongs to.
## Confidence
Every finding carries a confidence level. It is **derived from how the finding
was detected**, never estimated, so it can always be justified in one sentence:
* **`CONFIRMED`**: a candidate was detected and a required guard is provably
absent from the file, for example a Stripe webhook handler that never calls
`constructEvent`. This is evidence, though still not proof of exploitability.
* **`LIKELY`**: found in a qualifying context, but no absence was proven. Either
no guard marker exists for the rule, or the guard could live in an imported
module (a reserved-name deny-list, for instance). Read the code to settle it.
* **`NEEDS_REVIEW`**: a plain textual match. Context unverified. These are leads
for reading, nothing more, and there are many of them by design.
There are no percentages, and you must not invent any. Nothing in this tool
measures calibration, so a number would promise a precision that does not exist.
## Workflow
Follow this order. The scanner is an optional accelerator, not a gate: if it
cannot run on this machine, do the triage by reading the code yourself and
continue. Never report a grep hit as a confirmed vulnerability without reading
the actual code first.
### 1. Scope the repo
Find the project root and confirm the stack. Look for `package.json` (Next.js),
a `supabase/` directory or `@supabase/*` imports, and `stripe` usage. Note
whether the app uses the App Router (`app/`) or Pages Router (`pages/`), and
whether there are server actions, route handlers, or both. If the stack is not
Next.js + Supabase + Stripe, say so plainly and adapt: the 7 categories still apply,
but the specific patterns in the reference files may not match. The scanner also
detects multi-tenant signals (a tenant or org table, Host or subdomain routing, a
customer-domains table); `TENANCY` is only in scope when at least
one is present, and is skipped otherwise.
### 2. Run the scanner (optional fast first pass)
The scanner gives candidate findings in seconds. It is a convenience, not a
requirement. The full audit comes from reading the code against the 7 categories,
so if it does not run on this machine, do not stop: go to step 3 and do the
triage yourself by reading the code.
```bash
node scripts/cli.js <path-to-repo>
```
It needs Node and nothing else: the engine is vendored in this skill, so there
is no `npm install` and no network call. Any repo worth auditing here is a
Next.js app, so Node is already on the machine.
Useful flags:
```bash
node scripts/cli.js <repo> --format json # machine-readable, for CI or tooling
node scripts/cli.js <repo> --ci # exit non-zero on a CONFIRMED finding
```
With `--ci`: `0` nothing confirmed, `1` confirmed P2/P3, `2` confirmed P1,
`3` confirmed P0, `4` usage error. **Only `CONFIRMED` findings affect the exit
code**, so a pipeline is never broken by an unverified grep hit.
If Node is unavailable, say so in one line and proceed without it. The audit is
never blocked by a missing scanner.
Treat every finding as a lead to verify, including `CONFIRMED` ones: the engine
proves that a guard is missing from a file, not that an attacker can reach it.
On a multi-tenant app the `TENANCY` rules run too; on a single-tenant app they
are skipped and listed under "not applicable", so they create no noise.
If you skip the scanner, your step 3 reading must cover all 7 categories from
scratch rather than starting from findings. Use the reference files as your
checklist so nothing is missed.
### 3. Verify against the reference files
For each category with findings, and for every category regardless if the app is
about to handle real money or real users, read the matching reference and verify
by reading the actual code:
* `references/auth-and-isolation.md`: `AUTH` and `DATA`. Server-side auth on
routes and actions, middleware fail-open, Supabase RLS, object ownership
(IDOR), mass assignment, CSRF on route handlers, open redirect after auth.
* `references/payments.md`: `BILLING`. Stripe webhook signature and idempotency,
subscription state as source of truth, checkout and guest-checkout races,
downgrades and refunds.
* `references/abuse-validation-config.md`: `INPUT`, `CONFIG`, `ABUSE`. Input
validation, SSRF, rate limiting, quota races, unbounded cost, secrets and env,
CORS, Supabase Storage, degraded-mode behavior.
* `references/tenant-isolation.md`: `TENANCY`, multi-tenant only. Read this only
when the app is multi-tenant.
Read a reference only when you reach its category. This keeps context lean.
A finding is real only if you can point to the exact file and line and explain
the concrete consequence ("an authenticated user can read row X belonging to
another user because the query filters by nothing"). If you cannot, downgrade it
to a note or drop it.
### 4. Write the report
Produce the report using `assets/REPORT_TEMPLATE.md` exactly. Prioritize by
severity. For every finding give: the rule id, the category, the confidence, the
file and line, what an attacker or unlucky user can do, and a concrete fix. Write
the fix as remediation, never as a working exploit.
Do not carry a `NEEDS_REVIEW` finding into the report as though it were real.
Either you read the code and confirmed it, in which case say so, or you drop it.
The scanner's job is to point your reading; the report is your verdict, not its.
## Severity model
* **P0: Ship blocker.** Any authenticated or anonymous user can read or write
data that is not theirs, or obtain paid access without paying, or cause money
loss. Fix before shipping, full stop.
* **P1: Fix this week.** Exploitable but needs a specific condition (a known
id, a race window, a misconfigured env). Real risk, slightly higher bar.
* **P2: Hardening.** Not directly exploitable today but one refactor away from
P1, or missing defense in depth (no rate limit, no idempotency key yet).
* **P3: Hygiene.** Secrets in logs, dead config, weak CORS on a non-sensitive
route, TODOs near auth.
For `TENANCY`: a cross-tenant data read or write, or a session shared across
tenants, is a P0 (a stranger reaching data that is not theirs). A missing domain
deprovisioning on downgrade or a missing reserved-name deny-list is typically P2.
Severity and confidence are independent axes. A rule's severity says how bad it
would be if real; its confidence says how sure the detection is. A `P0` at
`NEEDS_REVIEW` is not a ship blocker until you have read the code and confirmed
it, which is exactly why the CI exit code ignores unconfirmed findings.
If you are unsure between two levels, state the assumption that decides it rather
than guessing silently.
## Output discipline
* Lead with the count by severity and the single most important thing to fix.
* No filler. Every finding earns its place.
* If a whole category is clean, say so in one line. Clean categories build trust.
* Never invent a finding to pad the report. If the repo is solid, say it is
solid and stop.Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!