Vibe Coding Security: Secrets, Auth and Supabase RLS
Vibe coding security for a real app: what to lock down before launch
Updated
Vibe coding security means closing the gaps that a coding agent leaves open by default. This page is Step 6 of 12 in the Vibe Coding guide. You leave with a launch checklist for Bookly, a Supabase policy you can test, and rules for keeping secrets and agent permissions under control.
The running example is Bookly, a booking app for a small hair salon. Clients book a slot and pay a deposit, and the owner sees the day's bookings. This page locks Bookly down in four places: client data behind row level security, the payment secret key on the server, the payment webhook checked before a booking changes, and the agent's own database and tool access.
Supabase changed its Data API grants in 2026, so each table now needs a grant, row level security, and policies. The sections below give the dates, the policy patterns and tests that stop silent failures, and the agent and MCP risks that now sit next to your code.
Related guides: TDD & Testing, Architecture, Getting Started, Deployment & Ops.
Launch Checklist
Vibe coding security checklist before you launch Bookly
Run this list once before the first client books a slot. Each line points to the section that explains it. The tests that prove these checks hold are on the TDD page.
.env file is in .gitignore. Only a template with empty values is committed.NEXT_PUBLIC_ or VITE_ prefix. Secrets stay in server environment variables.Secrets & Keys
The Vibe Coding Security Crisis
28.65 million new hardcoded secrets reached public GitHub commits in 2025, up 34% from the year before. GitHub reported more than 39 million secrets leaked across GitHub in 2024. That counter is different, so do not compare the two figures. nearly 44% of AI-generated code fails Veracode's security tests. Wiz reported in September 2025 that 1 in 5 organizations building on these platforms are exposed to risk.
How Secrets Leak
- Committed
.envfiles: A missing.gitignoreentry lets the real file reach the repository. - Client-side exposure:
NEXT_PUBLIC_andVITE_prefixes bundle secrets into browser-visible JavaScript. - AI chat context: Pasting API keys into AI prompts can potentially leak them to third parties.
- Hardcoded in source: AI often writes
const API_KEY = "sk-..."directly into code. - Git history:
git rm .envremoves the file, but the key stays in commit history until you rewrite it. Rotate the key first. - MCP and editor config files: GitGuardian found 24,008 unique secrets in MCP-related config files on public GitHub. Of those, 2,117 were valid.
Real-World Cost
Minutes, not days
Bots find keys pushed to GitHub within minutes. The 2023 honeypot tests saw leaked keys used within 2 minutes (Orca), and first abuse within 1 minute (Comparitech). Deleting the commit does not help. Rotate the key.
LLMjacking: up to $46,000 per day
Attackers steal exposed API keys to run their own LLM workloads. You pay the bill. Sysdig's 2024 worst-case estimate was up to $46,000 per day.
The .env Setup (Do This First)
# .gitignore (MUST include these)
.env
.env.local
.env.production
.env*.local
# .env.example (commit this template)
DATABASE_URL=
STRIPE_SECRET_KEY=
OPENAI_API_KEY=
# Never commit actual values
Check required variables at startup
Validate required environment variables with a schema when the server starts. A missing secret then fails at startup, not on the first live request. The env var rules themselves are on the getting started page.
Secret Scanning Tools
TruffleHog
Detects over 800 secret types and tells you whether each one still works. Scans git history, not just current files. Licensed under AGPL-3.0.
RecommendedGitleaks
Feature complete, so it gets security patches only. Its author now builds Betterleaks (MIT), which adds opt-in live checks with --validate. The pre-commit hook still works.
GitHub Secret Scanning
Secret Protection is free for public repositories, and push protection is on by default there. It blocks commits that contain known secret patterns. Private repositories pay per active committer. GitHub's example rate is $19 per active committer (USD).
Free for public reposHow to Rotate Leaked Keys
Revoke Immediately: Delete the key in the provider dashboard (AWS, OpenAI, Supabase). Every minute it stays live, bots can use it. Supabase revokes secret keys it finds in public GitHub repositories, but do not rely on that.
Rotate: Create a new key with the smallest scope. Update .env.local and the host's environment settings (Vercel or Railway).
Clean History: After the old key is dead, remove the file from git history with git filter-repo, as GitHub's guide describes. git rm is not enough. The workflow page covers the full cleanup.
Scan: Run TruffleHog against the full repo, history included, to find other live secrets.
API Key Rotation Schedule
A schedule this guide recommends. It is a practice, not a compliance rule.
Production Keys
90 days
Stripe, OpenAI, database credentials
Development Keys
6 months
Test API keys, staging DB passwords
Team Member Departure
Immediate
Rotate every key the departing member had access to
Suspected Compromise
Immediate
Revoke first, investigate second, generate new keys third
Blast Radius Containment
Limit damage when (not if) something goes wrong. Host settings and monitoring are covered on the deployment page.
OpenAI
Use prepaid credits and set hard limits at the organization and project level. When credits run out, the API returns a billing error. Prepaid credits expire after one year.
AWS
Set budget at $10. Alert at 80%. Use Service Control Policies to prevent launching expensive resources.
Stripe
Monitor dashboard daily for unusual charges and potential fraud patterns.
All Keys
Least privilege per environment. Dev keys should have minimal permissions. Never use production keys locally. Rotate every 90 days.
Auth, IDOR & Supabase RLS
Authentication vs. Authorization
Authentication is "Who are you?". Authorization is "What can you do?". AI often builds the login (Auth) and skips the permission check (Authorization). That gap leads to IDOR.
Auth Provider Comparison
| Provider | Free Tier | Paid | Notes |
|---|---|---|---|
| Clerk | Hobby: 50,000 MRU per app | Pro: $25/mo, or $20/mo billed annually. Extra MRUs $0.02/mo each (50,001 to 100,000) | Drop-in UI components. Lower rates at higher volume |
| Supabase Auth | 50,000 MAU. Free projects pause after 1 week of inactivity | Pro: 100,000 MAU included, from $25/month. $0.00325 per MAU above that | Integrated with the Supabase database and its RLS policies |
| NextAuth/Auth.js | Free (open source) | Self-hosted | Now maintained by the Better Auth team, which recommends Better Auth for new projects |
| Lucia | Free (open source) | Self-hosted | Deprecated March 2025. Do not start new projects on it |
| Auth0 | Up to 25,000 MAU, $0/month | Essentials (B2C) from $35/month | Enterprise-grade. Expensive at scale |
CVE-2025-29927: One Header Skipped Middleware Auth
The x-middleware-subrequest header let a request skip middleware-based auth checks. Vercel disclosed it on February 27, 2025, and rated it critical. Patches shipped in 12.3.5, 13.5.9, 14.2.25 and 15.2.3. Self-hosted next start and output: standalone builds were affected. Vercel, Netlify and Cloudflare Workers hosting were not.
Lesson: Verify auth in each route handler and server action, not only in middleware. Vercel's own advice: "We do not recommend Middleware to be the sole method of protecting routes."
More middleware bypasses in 2026
CVE-2026-44575 (published May 13, 2026) let requests skip middleware or proxy checks through .rsc and segment-prefetch URLs in the App Router. Fixed in 15.5.16 and 16.2.5. CVE-2026-64642 (published July 27, 2026) let crafted requests bypass middleware or proxy auth in App Router apps that use Turbopack with a single locale, in versions 16.0.0 to 16.2.10. Fixed in 16.2.11.
React2Shell: CVE-2025-55182 (December 2025)
An unsafe deserialization flaw in React Server Components allowed remote code execution, rated CVSS 10.0. Fixed in React 19.0.1, 19.1.2 and 19.2.1. Next.js fixed it in 15.0.5, 15.1.9, 15.2.6, 15.3.6, 15.4.8, 15.5.7 and 16.0.7. An AI app builder can pin an old framework version in a template, so check the lockfile.
IDOR (Insecure Direct Object Reference)
The server returns a record for an ID in the request without checking that the signed-in user owns it. AI often builds the login and leaves out this check.
Vulnerable
GET /api/bookings?id=124Client A changes the ID to 124 and sees client B's booking in Bookly.
Secure
GET /api/me/bookingsThe server reads the client from the session token. The request cannot name another client.
IDOR Test (30 Seconds)
- 1.Log in as User A. Open DevTools Network tab.
- 2.Find an API request that includes a user ID or resource ID.
- 3.Copy the URL. Change the ID to a different number.
- 4.Paste it in a new browser tab. If you see another user's data, your app has IDOR.
- 5.Repeat the test with PATCH and DELETE requests. A change to another user's record must fail.
Authorization Middleware Pattern
Wrap all protected API routes with a reusable auth check. Never rely on client-side checks alone.
Client-Side Only (Insecure)
// User can edit JS to bypass this!
if (user.role !== "admin")
return <div>Access denied</div>;
Server-Side Middleware (Secure)
// Reusable auth wrapper
export function withAuth(handler) {
return async (req) => {
const session = await getSession();
if (!session) return 401;
req.user = session.user;
return handler(req);
};
}
Common AI-generated auth mistakes: Missing RLS in Supabase, client-side only checks, no email verification, weak password requirements (accepts "123456"), API routes with no session check, and session tokens in localStorage (vulnerable to XSS). Middleware can redirect a visitor, but each route handler still checks the session, because middleware can be bypassed.
Supabase RLS policies for Bookly: write them, test them, avoid silent failures
Row level security (RLS) is the database's ownership check. For Bookly, a client reads only the bookings that belong to that client's account.
Supabase Data API grants: the change dated May 30 and October 30, 2026
- New projects, from May 30, 2026: a new table in
publicis no longer granted to the Data API by default. - All existing projects, from October 30, 2026: the same change applies. Tables that already exist keep their grants.
- Each table needs three things: a grant for the role,
enable row level security, and policies. - A missing grant returns error 42501 (permission denied) before RLS runs. Do not "fix" that with
grant all to anon. That reopens the table.
Bookly: lock bookings to their owner
-- Clients read only their own bookings
alter table public.bookings enable row level security;
grant select on public.bookings to authenticated;
create policy "Clients read own bookings"
on public.bookings for select to authenticated
using ((select auth.uid()) = client_id);
create index bookings_client_id_idx on public.bookings (client_id);
The grant lets the Data API reach the table. RLS and the policy decide which rows the client sees. Add insert, update or delete grants and policies only for the actions Bookly needs.
Common Mistakes
- Enabling RLS with no policy. The publishable key then sees no rows, and the app looks broken.
- Using the secret key (legacy name:
service_rolekey) in frontend code. A secret key bypasses RLS when the request has no user access token. - Writing
using (true), orto anon using (true). Every visitor then reaches every row the policy covers. - Creating tables in the SQL Editor or in migrations without
enable row level security. Tables made in the Table Editor get RLS by default. Other tables do not. - Views. A view bypasses RLS by default. On Postgres 15 and later, set
security_invoker = true. - Policies based on
user_metadata. Users can edit it. Store authorization data inapp_metadata. - An UPDATE with no matching SELECT policy. The update can match zero rows and return no error.
Silent failures look like missing data
With RLS on and no matching policy, a select returns an empty list and no error. Bookly's screen then shows no bookings, and the bug looks like missing data. Test every action as a signed-in client and as an anonymous visitor.
Quick RLS test: run it on every table
Run this from a terminal with no user login. Rows in the reply mean anonymous visitors can read them. A 42501 error is a safe result. Repeat it for every table, and send POST, PATCH and DELETE requests too. The Moltbook exposure included write access, so test writes as well.
curl "https://your-project.supabase.co/rest/v1/bookings" -H "apikey: YOUR_PUBLISHABLE_KEY"Let Supabase check the project for you
Supabase's Security Advisors, built on the open-source linter Splinter, flag tables without RLS, loose policies and exposed sensitive columns. They email weekly summaries to organization owners, and the Supabase MCP server can read them. Storage files need policies on storage.objects. Realtime also respects RLS, so a subscription receives only the rows its policy allows.
Key names: publishable and secret keys
Publishable keys (sb_publishable_...) and secret keys (sb_secret_...) replace the legacy anon and service_role keys. Legacy keys are deprecated by the end of 2026. Creating a new key does not revoke the old one, so disable the legacy key in Settings > API Keys. Keep the secret key on the server. Give each backend component its own secret key, so one leak means one rotation. Wiz's wording: a publishable key is safe to expose "when RLS is properly configured", because it works as a project identifier.
Input Validation with Zod
Never trust the client. Validate everything on the server.
// Server-side validation schema
const UserSchema = z.object({
email: z.email(),
name: z.string().min(1).max(100),
password: z.string()
.min(8, "Password must be at least 8 characters")
.regex(/[A-Z]/, "Needs uppercase")
.regex(/[0-9]/, "Needs number"),
};
// Always use safeParse, never parse (which throws)
const result = UserSchema.safeParse(req.body);
if (!result.success) {}
return res.status(400).json({errors: result.error.flatten()});
};
// result.data is now fully typed and validated
const user = result.data;
// URL validation
z.string().url()
// Enum values
z.enum(["free", "pro", "enterprise"])
// Optional with default
z.string().optional().default("user")
// Age range
z.number().int().min(13).max(120)
Validation is not an XSS fix
Stripping angle brackets with .transform() does not prevent XSS. React escapes text by default, so validate the shape and size of the data here, and handle HTML in the render step.
XSS Prevention
Risk comes from dangerouslySetInnerHTML and javascript: links. Keep user content out of both, and sanitize it if you must render HTML.
SQL Injection
Query builders and ORMs such as Drizzle, which Bookly uses, parameterize queries for you. Never concatenate user input into SQL strings.
Rate Limiting
Add rate limiting to login, signup, and password reset. Use @upstash/ratelimit with Redis.
SQL Injection: Bad vs. Good
// DANGEROUS: Raw SQL concatenation
const query = `SELECT * FROM users
WHERE email = '${email}'`;
// Attacker input: ' OR '1'='1
// Returns ALL users!
// SAFE: Parameterized query
const query = `SELECT * FROM users
WHERE email = $1`;
await db.query(query, [email]);
// SAFE: Drizzle (parameterized)
await db.select().from(clients)
.where(eq(clients.email, email));
XSS Prevention in React
// DANGEROUS: Never do this
<div dangerouslySetInnerHTML={
{__html: userInput}}
/>
// SAFE: If you must render HTML, sanitize first
import DOMPurify from 'dompurify';
<div dangerouslySetInnerHTML={
{__html: DOMPurify.sanitize(userInput)}}
/>
React auto-escapes text content by default. XSS only becomes a risk when you use dangerouslySetInnerHTML or inject content via href="javascript:..." attributes.
CSRF note: State-changing requests need CSRF protection too. Check your framework's documentation for the defaults it ships before you rely on them.
File Upload Security
File uploads are a common attack vector. Never trust the file extension or client-side MIME type.
const FileSchema = z.object({
file: z.instanceof(File)
.refine(f => f.size <= 5_000_000,
"Max file size is 5MB")
.refine(f =>
["image/jpeg", "image/png"].includes(f.type),
"Only JPEG and PNG allowed")
});
// Verify MIME by reading file bytes
import {fileTypeFromBuffer}from 'file-type';
const type = await fileTypeFromBuffer(buffer);
if (!type || !ALLOWED.includes(type.mime))
throw new Error("Invalid file type");
Scan uploads for malware, for example with ClamAV, before any other user can open them.
Server-Side Proxy Pattern
Never call external APIs directly from client-side code with exposed keys. Instead, create a server-side API route that makes the call. Your frontend calls your server route, which calls the external API with the secret key. The key never reaches the browser. This is the proper alternative to prefixing secrets with NEXT_PUBLIC_ just to make something work.
Payments: keep the secret key on the server and check the webhook
Bookly takes a deposit when a client books a slot. The payment provider sends a webhook when the payment settles, and that webhook decides the booking status.
Secret key stays on the server
The payment secret key lives in a server environment variable. It never appears in client code, a browser response, a log line, or a prompt pasted into an AI tool.
Verify the webhook before anything changes
Check the webhook signature on the raw request body, with the signing secret, before you change any data. Reject a request that fails the check. A webhook can arrive twice, so mark the booking paid once, keyed on the event ID.
Bookly's rule
A deposit moves a booking to paid only after the signed webhook arrives. The browser's success redirect changes nothing, because the visitor controls that page.
Owner refunds
The owner's refund goes through a server route with the same rules: the owner's session is checked, the secret key stays on the server, and the logs hold no card data.
Where the webhook URL is hosted, and how spend limits are set, is covered on the deployment page.
Agents, MCP & Prompt Injection
The coding agent is part of the attack surface
An agent that reads your repository, runs shell commands and holds database access can be steered by the text it reads. Bookly's agent reads booking notes, support tickets and issue titles. The OWASP Top 10 for Agentic Applications (2026) names the risks. The ten items below show where Bookly's agent is exposed.
OWASP Top 10 for Agentic Applications, applied to Bookly
Ten risk items, in OWASP's ASI numbering.
ASI01: Agent Goal Hijack
Text in a booking note or a support ticket changes what the agent tries to do.
ASI02: Tool Misuse and Exploitation
The agent calls a database or shell tool in a way you did not intend, such as a broad query.
ASI03: Identity and Privilege Abuse
The agent uses a credential with more rights than its task needs.
ASI04: Agentic Supply Chain Vulnerabilities
A package, MCP server or agent tool that you install carries bad code.
ASI05: Unexpected Code Execution
The agent runs code that it generated or fetched, on your machine or on the server.
ASI06: Memory and Context Poisoning
Bad text saved in the agent's memory or in a context file changes its later work.
ASI07: Insecure Inter-Agent Communication
One agent passes unchecked output to another agent, which then acts on it.
ASI08: Cascading Failures
One wrong step spreads through a chain of tool calls and agents.
ASI09: Human-Agent Trust Exploitation
A reviewer approves an action because the agent's explanation sounds right.
ASI10: Rogue Agents
An agent keeps acting outside its task after the task is over.
Prompt injection and the lethal trifecta
Prompt injection is the attack on an agent through text it reads. These design rules limit the damage.
Prompt injection
Supabase's MCP guidance calls prompt injection "the primary attack vector unique to LLMs". Hidden instructions in user content, such as a support ticket, can make an assistant run harmful queries.
The lethal trifecta
Simon Willison's term for three things together: access to private data, exposure to untrusted content, and a way to send data out. An agent with all three can be turned into a data-theft tool by one injected prompt.
The Rule of Two
Meta's rule: without human approval, let an agent have at most two of the three properties in the trifecta. For Bookly's agent, that means the agent never holds private data, untrusted input and an outbound channel at the same time without a person in the loop.
OWASP LLM Top 10 (2025)
For an app that calls an LLM, LLM01 Prompt Injection and LLM06 Excessive Agency matter most. The 2025 edition is the current one on OWASP's page. OWASP maps prompt injection to six of the ten agentic categories.
Supabase MCP server: the settings that limit the agent
The MCP server gives the agent database tools. Each setting below narrows what the agent can reach.
# Point the agent at one development project, read-only, with two tool groups
<your Supabase MCP server URL>?project_ref=<dev-project-ref>&read_only=true&features=database,docs
read_only=trueruns queries as a read-only Postgres user.project_ref=<id>scopes the server to one project and disables account-level tools.features=database,docslimits which tool groups the agent can use.- Keep manual approval of tool calls on. A confirmation dialog is a guardrail, not a guarantee.
- Use a database branch or a development project for agent work. Connect production only when the task needs production data.
- Do not give the MCP server to your customers.
Tool permissions and repository trust
Give each agent the smallest set of rights, and keep secrets out of any folder an agent can read.
- Scoped, short-lived credentials: Give each agent its own credential with the rights its task needs. Never give it your owner-level key.
- No real secrets in the project folder: An agent that reads the folder reads the secrets in it too.
- Unknown repositories: Do not open a repository you do not know in an agent. Read its project files first.
- MCP servers: Read what an MCP server does before you install it, and pin its version.
- Updates: Keep the agent and its extensions up to date.
Secure vibe coding: rules the agent follows before it writes code
Tell the agent these rules before the first task, and check each one in review. The CSA secure vibe coding guide makes the same point: ask for env vars, validation and auth explicitly.
- 1.Secrets come only from server environment variables. No secret appears in client code or in a committed config file.
- 2.Every input is validated on the server with a schema before it touches the database.
- 3.Every query checks that the signed-in user owns the row. Auth is checked in each handler, not only in middleware.
- 4.Rate limits cover login, signup, password reset and each AI call.
- 5.The agent lists each security check it added, so the reviewer can test each one.
Evidence, Breaches & OWASP
Compliance Quick Reference
Privacy law, cookie consent, open-source licenses and accessibility rules are covered on the legal page. This page covers the security controls that those rules assume you already have.
Academic Evidence on AI Code Security
Published studies show that AI assistance can produce less secure code and more confidence in that code.
Perry et al. (ACM CCS 2023)
Participants with an AI assistant wrote significantly less secure code. They were more likely to believe their code was secure than the group without AI.
Perry et al., arXiv 2211.03622Pearce et al. (IEEE S&P 2022)
Of 1,689 programs generated with GitHub Copilot across 89 scenarios, about 40% were vulnerable.
1,689 programsVeracode 2026 GenAI report (July 28, 2026)
Average security pass rate about 56%, virtually unchanged from last year's report. Python passed most often at 63%, and Java least often at 30%. Syntax passed at about 100%.
100+ models trackedCopilot Code Review Study
Copilot's code review frequently fails to detect critical vulnerabilities such as SQL injection. It also misses XSS and insecure deserialization. Its feedback mostly targets low-severity issues.
arXiv 2509.13650The Overconfidence Trap
In the Perry et al. study, the participants with AI access felt more sure that their code was secure, and their code was less secure. Plan for review and runtime tests. Confidence is not evidence.
The vibe coding security risks that caused real leaks, 2025 to 2026
Each case names the cause that its source reports.
Moltbook (disclosed January 31, 2026)
A vibe-coded social network for AI agents. A Supabase publishable key in client JavaScript and no RLS gave anyone read and write access. Exposed: about 1.5 million agent API tokens, about 35,000 email addresses and 4,060 private DM conversations, some with plaintext OpenAI keys. The key was not the bug. Missing RLS was. Wiz reports five fix rounds in about three hours, and each round exposed more tables. Lesson: test writes, and retest every table after each fix.
UpGuard: public Supabase tables (October 14, 2026)
From about 300,000 domains with signs of Supabase use, UpGuard found 16,326 databases with publicly readable tables. Over half showed signs of personal data. UpGuard calls it a configuration problem (grants and RLS), not a Supabase flaw, and it has not confirmed malicious access.
RedAccess: apps public by default (May 2026)
A scan of about 380,000 publicly reachable assets on Lovable, Base44, Replit and Netlify found about 5,000 (1.3%) that held sensitive corporate data, such as hospital summaries and internal finance data. Some apps were public by default. A missing login on an internal tool is a second leak class, after RLS.
Base44 authentication bypass (2025)
Wiz reported that the register and verify-otp endpoints were open. The non-secret app_id was enough to create a verified account on a private app and bypass SSO. Wix fixed it within 24 hours.
Lovable apps with weak RLS (CVE-2025-48757)
Researchers reported 170+ apps built with Lovable that had missing or weak RLS, which exposed user data.
Escape.tech: scanning 5,600 vibe-coded apps
Escape.tech scanned more than 5,600 publicly available vibe-coded apps and reported:
- More than 2,000 vulnerabilities
- 400+ exposed secrets
- 175 instances of exposed personal data
OWASP Top 10:2025 Mapped to AI-Generated Code
Built on about 175,000 CVE-to-CWE records from NVD and over 2.8 million applications in the contributed data, mapped to 643 CWEs. Each item below shows how it appears in vibe-coded apps.
A01: Broken Access Control
AI often skips the ownership check, so IDOR appears. SSRF, which had its own category before, now sits inside A01.
A02: Security Misconfiguration
Default settings shipped to production, debug endpoints left on, and Supabase or Firebase deployed without security rules. The category rose from fifth place to second in the 2025 list.
A03: Software Supply Chain Failures
Expands the earlier "Vulnerable and Outdated Components" category to dependencies, build systems and distribution. AI can suggest packages that do not exist. Attackers register those invented names. In a 2025 USENIX Security study, commercial models hallucinated about 5.2% of package names and open-source models about 21.7%. A May 2026 study of five frontier models reported 4.62% to 6.10%. For Bookly, run npm audit before each release (or pip-audit on a Python project), check that every package the agent adds exists and is the one you meant, and turn on Dependabot alerts in the GitHub repository so a vulnerable dependency opens a pull request.
A04: Cryptographic Failures
Hardcoded JWT secrets such as "supersecretkey", weak hashing such as MD5 or SHA1, and missing encryption at rest.
A05: Injection
AI uses string concatenation instead of parameterized queries, which produces SQL injection. Use parameters or an ORM for every query.
A06: Insecure Design
No threat model, and designs that cover only the happy path. The AI satisfies the prompt without the wider security context.
A07: Authentication Failures
No rate limit on login, weak password rules, and JWT handling that accepts the "none" algorithm.
A08: Software or Data Integrity Failures
Webhooks accepted without a signature check, and no dependency integrity checks.
A09: Security Logging & Alerting Failures
No audit trail for logins, permission changes and admin actions. Or the opposite: passwords and tokens written to logs.
A10: Mishandling of Exceptional Conditions NEW in 2025
Errors that leak stack traces, fail open instead of closed, or skip try/catch around third-party calls. The category has 24 CWEs.
JWT Mistakes AI Consistently Makes
Eight recurring JWT mistakes in AI-generated code, and the fix for each.
Wrong Algorithm
AI generates HS256 (symmetric) when RS256 (asymmetric) is needed for distributed systems. HMAC is less secure here because verification doesn't require a private key.
Outdated JWT Library
Old JWT packages stop receiving fixes. Check the lockfile for old versions, and use a maintained library.
Hardcoded Secrets
A placeholder secret such as "supersecretkey" committed to source is a secret anyone can copy. Load the secret from the server environment, and use a different one per environment.
"none" Algorithm Not Blocked
AI code often doesn't validate the alg header claim. Attackers set alg: "none" to bypass signature verification entirely, creating unsigned tokens.
Missing Claims Validation
AI decodes tokens without verifying exp (expiration), iss (issuer), and aud (audience) claims. Expired or misrouted tokens are accepted.
Algorithm Confusion Attacks
AI code accepts both symmetric and asymmetric algorithms. Attackers exploit RS256 to HS256 confusion, using the public key as an HMAC secret to forge tokens.
Token in localStorage
AI stores JWT in localStorage, which any script on the page can read (XSS vulnerability). Tokens belong in httpOnly cookies that JavaScript can't access.
Missing Token Lifecycle
No refresh token rotation, no token revocation, no expiration dates. Once issued, tokens work forever, even after password changes or account compromise.
Secrets Management by the Numbers
GitGuardian's 2026 report, which covers 2025 data, and IBM's 2025 breach report. Leaks are growing.
28.65M
New hardcoded secrets in public GitHub commits (2025)
Up 34% year over year
1,275,105
AI-service secrets found in 2025
Up 81%
3.2%
Commits assisted by Claude Code that leaked secrets
Baseline across public GitHub commits: 1.5%
24,008
Unique secrets in MCP-related config files
2,117 of them valid
Above 64%
Credentials from 2022 still valid in the January 2026 retest
GitGuardian retest
$4.44M
Global average cost of a data breach, USD (IBM, 2025)
Down 9% from $4.88M in the 2024 report
Key pattern: AI-service keys are the fastest-growing leak class, and MCP config files are a new place where keys leak. Keys that leak stay valid for a long time, so rotate on any exposure.
Rate Limiting Tools and Thresholds
Add rate limits to every endpoint that accepts user input or costs money.
express-rate-limit
Free. It had 93,253,875 npm downloads in the week of October 22 to 28, 2026. A common choice for Node.js and Express apps.
Gotcha: Behind proxies (Heroku, AWS, Cloudflare), you must set app.set('trust proxy', 1) or the limiter becomes global, blocking everyone.
@upstash/ratelimit
The SDK is free. Redis is billed at $0.20 per 100K commands, and the free tier includes 500K commands per month. Supports sliding window, fixed window, and token bucket algorithms.
Best choice for Next.js + Vercel serverless deployments.
ServerlessCloudflare Rate Limiting
Rules per plan: Free 1, Pro 2, Business 5, Enterprise 100 (contract-dependent). Free and Pro count by IP only. Blocking happens at the edge, before requests reach your origin.
No code changes needed.
No Code| Endpoint | Limit | Scope |
|---|---|---|
| Login | 5 attempts / 15 min | Per IP |
| Signup | 3 / hour | Per IP |
| Password Reset | 3 / hour | Per email |
| API Endpoints | 100 / minute | Per user |
| File Uploads | 10 / hour | Per user |
| Expensive Ops (AI calls) | 5 / minute | Per user |
Audit Workflows & Prompts
Multi-LLM Security Audit Approach
Different AI models miss different vulnerability types. Using one model for security review leaves blind spots.
Semgrep Study (September 2025)
Claude Code (v1.0.32, Sonnet 4) and Codex (v0.2.0, o4-mini) were tested on 11 Python apps. Claude found 22% of IDOR bugs (13 of 59) but 5% of SQL injection (2 of 38). Codex found 47% of path traversal bugs (8 of 17) and 0% of IDOR (0 of 5). The IDOR sample is small, and both models are older versions. Do not read this as a current ranking.
Security Pass Rates (Veracode 2026)
Veracode's July 2026 report puts the average security pass rate at about 56%. Newer models write correct code more often, but not safer code, so review stays mandatory for every model.
"Review this code ONLY for security vulnerabilities. Assume a hostile internet environment. Check for: exposed secrets, missing auth, injection, XSS, IDOR, missing rate limiting, insecure defaults. For each finding, state the vulnerability type, the specific line(s), and the fix."
Generate code with your primary model (Claude, GPT, Cursor).
Run the security review prompt above with a different model than the one that wrote the code.
Combine findings from both models. Models have complementary blind spots, so the union of their findings is more comprehensive than either alone.
Layer static analysis (Semgrep, SonarQube) on top of AI review. Deterministic tools catch pattern-based vulnerabilities that all models miss.
Copy-Paste Security Audit Prompts
Three structured audit prompts. Run each with a different AI model than the one that wrote your code.
Copy this comprehensive 11-pass OWASP audit prompt and give it to an AI model. Covers reconnaissance, all OWASP 2025 categories, exploit chain analysis, and structured reporting.
Deep dive on auth. The most attacked surface in any web application.
SQL injection remains a top threat. ORMs and parameterized queries are non-negotiable.
Use a Different Model
Run these audits with a model from a different family than the one that generated your code. Models have blind spots for their own output. The Semgrep results show that the two models missed different bug types, so one model is not enough.
Next step: architecture
Step 7 of 12 covers the stack and the backend layer that Bookly uses, plus the choices that are hard to undo. Continue on the architecture page.
Common Questions
How do I enable RLS in Supabase?
Run alter table bookings enable row level security; then add a policy for each action, such as letting a client select only rows where client_id matches auth.uid(). Tables made in the dashboard Table Editor have RLS on, but tables made with SQL or migrations do not. On new projects, the table also needs an explicit grant before the Data API can see it.
Which vibe coding security risks are most common?
The common ones are secret keys in client code, missing ownership checks that let one user read another user's data, Supabase tables without RLS, unverified payment webhooks, and packages the AI invented. Agents add new risks: prompt injection through data they read, and over-broad tool or MCP permissions. Veracode's July 2026 report found about 44% of AI coding tasks introduced a risky vulnerability.
How do I secure the database in an app built with AI?
Keep the database behind your server or behind RLS, never open to the browser. Give each table RLS and a policy per action, keep the secret key on the server, and use parameterized queries through an ORM such as Drizzle. Give agents a separate, read-only development database, not production. Then test as a second user to confirm one client cannot read another client's bookings.
Why does Supabase return permission denied for a new table?
Each new table in public needs an explicit grant before the Data API can reach it, on top of enable row level security and your policies. Tables that already exist keep their grants. Make the agent write all three statements for every new table, and reject any fix that grants everything to anon, because that brings back the exposure the change removes.
I committed an API key to GitHub. What should I do first?
Rotate first. Honeypot studies from 2023 saw leaked keys used within 1 to 2 minutes of a GitHub push, so deleting the file later does not protect you. Revoke the key, issue a new one with the smallest scope, and update each service that uses it. Scan the full git history with TruffleHog, and rewrite history only after the old key is dead. Give each backend service its own key.
Can a coding agent with database access be prompt injected?
Assume any user-written text, such as a booking note or a support ticket, can contain instructions. Connect the Supabase MCP server with read_only=true, scope it with project_ref, limit tool groups with features, and keep manual approval on. Point it at a development project, and keep it away from production unless the task needs production data. A confirmation dialog is a guardrail, not a guarantee.