Next.js + Supabase + Stripe rules for AI coding agents
Two copy-paste Cursor rules (Supabase RLS and security, Stripe webhooks) and the 13-point pre-deploy audit checklist, so Cursor, Claude Code or Codex stop shipping tables without RLS, service keys in the browser and unverified webhooks.
Free below. The full kit adds the complete audit prompt, AGENTS.md, CLAUDE.md, two more rules, a deny list for .env files and a verification log. Get the full kit: $9 Also see the test results for our other paid packs.
Rule 1: Supabase RLS and security
Save as .cursor/rules/10-supabase-rls-security.mdc. Raw Markdown for agents: /guides/nextjs-supabase-stripe-rules.md.
--- description: Supabase migrations, RLS policies, grants, Postgres functions and the Supabase client globs: supabase/**/*.sql, lib/supabase/**, lib/data/** alwaysApply: false --- # Supabase: RLS and access rules ## Every table in an exposed schema (usually `public`) 1. `alter table ... enable row level security;` in the same migration that creates the table. 2. Set grants explicitly. Policies do not take grants away: `revoke all on table ... from anon, authenticated;` then grant back only what each role needs. 3. Write one policy per operation (select / insert / update / delete). Do not use `for all`. 4. Always name the role with `to authenticated` (or `to anon` only for data that is meant to be public). Never `to anon using (true)` on private data. 5. Wrap helpers: `(select auth.uid()) = user_id`, not `auth.uid() = user_id`. 6. `insert` needs `with check`. `update` needs `using` AND `with check`, plus a `select` policy. 7. Index every column a policy filters on (`user_id`, `team_id`). 8. Add `supabase/tests/<table>_rls.test.sql` (pgTAP) asserting allow and deny for anon and authenticated on each operation. Run `supabase test db`. A table is not done until it passes. ## Do not - Base a policy on `user_metadata` (users can edit it). Use `app_metadata` for authorization data. - Create a view without `with (security_invoker = true)` on Postgres 15+; views otherwise bypass RLS. - Write a `security definer` function without `set search_path = ''` (and schema-qualified names), or put one in an exposed schema. - Use `for all` or `using (true)` to "make the error go away". A `42501` error means check the grants first, then the policy. ## Keys - Browser and per-request server clients use the publishable (anon) key plus the user's session. They are subject to RLS. - The secret / `service_role` key bypasses RLS. It lives only in `lib/supabase/admin.ts` (`import 'server-only'`), is never `NEXT_PUBLIC_`, and is used only after you have checked the caller is allowed to do the thing. Source: https://supabase.com/docs/guides/database/postgres/row-level-security
Rule 2: Stripe checkout and webhooks
Save as .cursor/rules/20-stripe-webhooks.mdc.
---
description: Stripe Checkout, webhooks, signature verification and fulfillment
globs: app/api/stripe/**, app/api/checkout/**, lib/stripe*.ts
alwaysApply: false
---
# Stripe: checkout and webhooks
## Webhook route (`app/api/stripe/webhook/route.ts`)
- `export const runtime = 'nodejs'`.
- Read the RAW body with `await request.text()`. Never `request.json()` before verifying: Stripe signs the exact bytes, and any change to the body breaks verification.
- Verify with the official library before doing anything else:
`stripe.webhooks.constructEvent(rawBody, request.headers.get('stripe-signature')!, process.env.STRIPE_WEBHOOK_SECRET!)`
On failure return 400. Never skip verification "for local testing". Use `stripe listen` (it prints a local `whsec_`).
- Do not set the tolerance to 0 (that disables the replay check). Keep the default (5 minutes).
- Return a 2xx quickly. Do slow work after the verified event is stored or queued.
- Events can arrive twice and out of order. Store `event.id` (unique constraint) and skip ones already processed. Do not rely on event order or on `created`.
- Subscribe only to the event types you handle.
- Fulfillment (granting access, marking an order paid) happens from the verified webhook, not from the `success_url` page.
- Use the service-role/admin Supabase client here (there is no user session), after verification.
## Checkout
- Create the session on the server with a Price ID from your own config. Never accept an amount, price or currency from the client.
- Put your user id in `client_reference_id` or metadata from the server session, never from request input.
- Keep `STRIPE_SECRET_KEY` and `STRIPE_WEBHOOK_SECRET` server-only. Test and live keys differ, and so do their webhook secrets.
Source: https://docs.stripe.com/webhooks
The 13-point pre-deploy audit (checklist)
The full kit turns these thirteen checks into one read-only prompt: paste it into your agent and it returns a PASS / FAIL / UNVERIFIED table with file:line evidence, without editing anything. Here are the checks themselves; you can run them by hand or hand them to your agent as a list.
- 1. No secrets in the browser. No Stripe secret, webhook secret or Supabase secret/service_role key behind a NEXT_PUBLIC_ variable or in a 'use client' file.
- 2. Secrets not committed. .gitignore covers .env*; no .env files or key-looking strings in tracked files.
- 3. RLS on every table. Every table created in an exposed schema has row level security enabled in its migration.
- 4. Grants and policy shape. Explicit revoke-then-grant; no 'for all'; no 'to anon using (true)' on user data; one policy per operation, with a role.
- 5. Policy correctness. insert has with check; update has using and with check; (select auth.uid()); nothing based on user_metadata.
- 6. Functions and views. security definer functions pin search_path and stay out of exposed schemas; views use security_invoker.
- 7. Secret client confined. The secret-key Supabase client is server-only and used only after an auth check or a verified webhook.
- 8. Server Actions and route handlers. Each one checks the user, checks ownership of the specific resource, and validates input.
- 9. Stripe webhook verification. Raw body, signature checked with the endpoint secret before anything else, 400 on failure.
- 10. Webhook reliability. event.id stored and duplicates skipped; no dependence on event order; fulfilment driven by the webhook.
- 11. Money and identity from the server. Price IDs and user ids come from the server, never from the request body.
- 12. Tests exist and aren't rigged. pgTAP allow/deny tests per table; webhook tests for good, bad and duplicate; no .skip or placeholder assertions.
- 13. Framework patch level and image safety. next at 16.4.1+ (16.x) or 15.5.28+ (15.x), sharp 0.35.5+, lockfile committed, no SVG uploads, images.dangerouslyAllowSVG not true, tight images.remotePatterns. Run npm ls next sharp and npm audit yourself.
Security patching (Oct 2026)
Next.js shipped an out-of-band security update on 2026-10-14 (fixed in next@16.4.1 and next@15.5.28): a critical unauthenticated remote code execution in use cache functions that return multiple typed arrays (App Router with Cache Components, GHSA-673q-mjm6-r5xr, no known workaround); a critical remote code execution in the Image Optimization API via SVG on glibc-based Linux (CVE-2026-96889 / GHSA-2464-fhr4-w9xw, workaround: sharp 0.35.5 or later; a public proof of concept exists); and a high-severity denial of service in App Router Server Actions (GHSA-p3m5-59mp-jv5r). Sources: Next.js advisory, GHSA-2464-fhr4-w9xw.
- Check what you run.
npm ls next sharp(orpnpm ls/yarn why). You want next 16.4.1 or later on 16.x, or 15.5.28 or later on 15.x, and sharp 0.35.5 or later. The advisory lists every version from 10.0.0 to 15.5.27 and 16.0.0 to 16.4.0 as affected, and gives no fixed release for 14.x or older. - Never downgrade, always lock. Do not let an agent downgrade next or sharp to make a build pass. Commit the lockfile and install from it in CI (
npm ci). Do not uselatestfor next. - Audit before every deploy. Run
npm auditbefore deploying and treat a high or critical advisory in the framework, React or sharp as blocking. Avoidnpm audit fix --force, which can make major-version jumps. - Patch React Server Components and Node.js. React published two advisories on 2026-10-14 (the Next.js post links them as upstream): GHSA-xqc2-xf4c-x3h3 (critical remote code execution) and GHSA-h3gv-c5rc-p453 (high, denial of service). They affect the
react-server-dom-webpack,-parceland-turbopackpackages (notreactorreact-dom) and are fixed in 19.0.9, 19.1.10, 19.2.9 and 19.3.1; apps whose React code does not run on a server are not affected. For a Next.js App Router app, take the Next.js fix above. Node.js released security fixes on 2026-10-21 (ten CVEs, highest rated HIGH, including a Brotli decoder remote code execution innode:zlib, a DNS NUL-byte truncation and TLS hostname-verification bypasses): fixed in 22.23.4 (LTS), 24.22.1 (LTS) and 26.12.1 (Current). Node 20 and older are end-of-life and get no fix. Checknode -vand also your CI, Dockerfile and host settings. Sources: Node.js security releases, Node.js release schedule. - Install hygiene for npm. On 2026-10-08
tensorlake@0.5.144was published to npm through a compromised maintainer account (the safe release is 0.5.143): apreinstallscript stole GitHub and npm tokens, cloud keys, SSH keys and AI-tool configs, and republished itself into other packages. It carried valid npm provenance, so provenance alone does not prove a package is safe (vendor advisory, StepSecurity). Install withnpm cifrom the committed lockfile (it fails if the lockfile and package.json disagree and never rewrites the lockfile: npm docs). Use--ignore-scripts(orignore-scripts=truein .npmrc) for the first install of a new or unreviewed package or a freshly cloned repo. Tell your agent not to add, upgrade or swap a dependency without asking you, and to show the package'spreinstall,installandpostinstallscripts (npm view <name>@<version> scripts) and pin new ones to an exact version. - If a package looks malicious, do not revoke tokens first. Stop and report; do not let the agent revoke, rotate or delete tokens itself. The Tensorlake advisory says the malware installs a
gh-token-monitorservice that deletes the user's home directory if the stolen GitHub token is revoked, so you disconnect the machine and remove that service first, then rotate credentials from a different, clean machine (steps for Linux, macOS and Windows are in the advisory; also Socket). Also watch for unexpected.claude/settings.jsonor.vscode/tasks.jsonfiles and commits by a user called claude with the message "chore: update dependencies" that you did not make (StepSecurity). - Close the image door. No SVG uploads from users (reject image/svg+xml on the server and in Storage bucket mime types), keep
images.dangerouslyAllowSVGunset or false, keepimages.remotePatternsto exact hosts with no wildcards, never pass a user-supplied URL to next/image, and escape any text you put into an SVG or ImageResponse. This is defense in depth, not a substitute for upgrading.
The full kit's new rule, .cursor/rules/40-security-patching.mdc (plus lines in AGENTS.md and CLAUDE.md and check 13 of the audit, which still covers only next and sharp) makes your agent do these steps, now including the React, Node.js and npm install steps (v1.2, checked against the official pages on 2026-10-23). Honest limits: rules reduce risk, they do not make an app safe or prove you are patched. Version numbers will be superseded, so re-read the official advisory pages. We have not run the exploits, installed any malicious package or tested the rule against a vulnerable app; the gh-token-monitor behaviour is as reported by the vendor advisory and researchers, not tested by us. For context only: PHP also patched two critical remote code execution bugs on 2026-10-21 (CISA alert); that is a separate ecosystem.
Did we test it?
The Stripe webhook rule's code pattern was run for real: the exact route file from the kit's sample app, served by next dev (Next.js 16.4.3, stripe-node 23.0.1), with a stubbed database and throwaway test secrets, hit with signed and unsigned requests on 2026-10-23:
| Request | Response | Effect | |
|---|---|---|---|
| T1 | Valid signature, new event | 200 ok | Fulfilled once |
| T2 | Wrong signature | 400 Invalid signature | Nothing written |
| T3 | No signature header | 400 Missing signature | Nothing written |
| T4 | Same event id delivered again | 200 duplicate | Still fulfilled once |
| T5 | Valid signature, 1 hour old | 400 Invalid signature | Rejected by the default 5 minute tolerance |
| T6 | Control: the flawed route, forged unsigned event | 200 ok | An order was marked paid by a forged request |
Not tested: the SQL was only syntax-parsed (no Postgres or pgTAP run); nothing was run inside Claude Code or Cursor; one machine, one day; not against Stripe's servers. The audit prompt was run through one model on two sample apps and, on its first run, wrongly passed a placeholder test, so we tightened the wording and re-ran (both runs are in the paid kit's log).
Next.js + Supabase + Stripe Agent Rules Kit
AGENTS.md, CLAUDE.md (imports AGENTS.md), 5 scoped .mdc rules (core, Supabase RLS/security, Stripe webhooks, testing, security patching), a 13-point read-only pre-deploy audit prompt (PASS/FAIL with file:line), Claude Code permissions.deny for .env files, a read-only grep helper, two sample apps (one with planted flaws), and VERIFY-LOG.txt with the raw output of what we ran.
Get the full kit: $9 See how we testFAQ
What is this?
A free sample of our paid Next.js + Supabase + Stripe agent rules kit: two scoped Cursor .mdc rules (Supabase RLS and security, Stripe webhooks) you can drop into .cursor/rules/, plus the headings of the 13-point pre-deploy audit. The same text works as agent instructions in Claude Code, Codex and others, since the .mdc files are plain Markdown.
Does it make my app secure?
No. It makes an AI coding agent much less likely to repeat the classic mistakes: tables without RLS, the service key in the browser, webhooks that don't verify the signature. A human should review anything that handles money or personal data.
What is in the paid $9 kit that isn't here?
The full pack: AGENTS.md, CLAUDE.md (imports AGENTS.md), three more rules (core, testing and security patching), the complete 13-point read-only audit prompt with its PASS/FAIL output format, Claude Code permissions.deny for .env files, a read-only grep helper, two sample apps (one with planted flaws) and VERIFY-LOG.txt with raw output of our test runs.
Where do the rules come from?
From the official docs, linked below: the Next.js data security guide, Supabase's Row Level Security guide and Stripe's webhook docs. Each rule file names its source. Docs change, so re-check them when you upgrade.
How was it tested?
We ran the webhook rule's code under next dev (Next.js 16.4.3, stripe-node 23.0.1) with valid, bad, missing, stale and duplicate signatures (results below). The SQL was only syntax-checked with PostgreSQL's parser; the policies and pgTAP tests were not executed against Postgres, and nothing was tested inside Claude Code or Cursor. The full log ships with the paid kit.
Sources
- Next.js: data security
- Supabase: Row Level Security
- Stripe: webhooks
- Claude Code: permissions
- Next.js security update, 2026-10-14
- GHSA-2464-fhr4-w9xw (Image Optimization, CVE-2026-96889)
- Node.js security releases, 2026-10-21
- React advisory GHSA-xqc2-xf4c-x3h3 (critical RCE, React Server Components)
- React advisory GHSA-h3gv-c5rc-p453 (DoS, Server Functions)
- Tensorlake npm compromise advisory GHSA-8g63-53c9-65j2
- StepSecurity: tensorlake npm compromise
- npm docs: npm ci
- CISA: PHP critical vulnerabilities, 2026-10-21 (context only)
Install as an agent skill: npx skills add https://agentpromptsatoz.com --skill nextjs-supabase-stripe-rules