Four security bugs AI coding agents keep writing in Next.js + Supabase apps (and 4 false positives I fixed in my scanner)
I use coding agents (Claude Code, Cursor, OpenCode) a lot, and after a while you notice that the bugs they write are not the bugs a tired human writes. A human forgets to escape one input. An agent writes a perfectly str
I use coding agents (Claude Code, Cursor, OpenCode) a lot, and after a while you notice that the bugs they write are not the bugs a tired human writes. A human forgets to escape one input. An agent writes a perfectly structured endpoint that has no authentication at all, reports the task as done, and moves on.
This post walks through the four patterns I kept running into in Next.js + Supabase projects, and how I turned them into rules for an open-source scanner called vibe-shield. Full disclosure: I am the author of that tool. Treat the last two sections as the pitch and everything before them as the actual content.
1. The service_role key that ends up in the browser
The Supabase service_role key bypasses Row Level Security. Agents know this in the abstract, but when they need an admin client they tend to do one of two things. They create lib/supabaseAdmin.ts and then import it from a component that has "use client" at the top (or from something that component imports). Or they reach for NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY because "it needed the env var".
// lib/supabaseAdmin.ts
import { createClient } from '@supabase/supabase-js'export const supabaseAdmin = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!
)
That file is fine on the server. This is the bug:
'use client'
import { supabaseAdmin } from '@/lib/supabaseAdmin'
The fix is to mark the helper with import 'server-only' so the build fails if a client component ever pulls it in, and to never prefix a secret with NEXT_PUBLIC_. The detection side is harder than it looks. Checking whether a file starts with "use client" is not enough, because the import is often transitive (a barrel file re-exports the helper, a hook imports the barrel, a component uses the hook). So the scanner builds a best-effort import graph from relative imports and tsconfig aliases and checks whether a client entry can reach a service-role client.
2. Mutation routes with no authentication
Ask an agent for "a delete endpoint" and you often get something like this:
// app/api/users/route.ts
export async function DELETE(req: Request) {
const { id, userId } = await req.json()
await db.query(`DELETE FROM users WHERE id = ${id} AND owner = ${userId}`)
return new Response('ok')
}
It works, and it has three problems at once: no authentication, SQL built by string concatenation, and the caller decides whose data gets deleted. A normal review loop rarely catches all three, because the code looks complete. The App Router makes this easy to miss, since every POST, PUT, PATCH and DELETE export is a public HTTP endpoint whether or not you meant it to be.
3. IDOR: trusting the request for who the user is
This is the third problem from the snippet above, and it deserves its own section because it survives even after you add authentication. The agent authenticates the user, and then still reads the ownership key from req.json():
const { data: { user } } = await supabase.auth.getUser()
if (!user) return new Response('Unauthorized', { status: 401 })
const { id, userId } = await req.json() // userId comes from the caller
await db.from('documents').delete().eq('id', id).eq('owner', userId)
The user is logged in, but they can pass any userId they like. The fix is to take the owner from the verified session (user.id), never from the body, query string or params.
4. Prompt injection sinks and poisoned agent config
The last pattern is two related problems. First, agents wire model output into dangerous sinks: eval, new Function, dangerouslySetInnerHTML, or a tool execute that shells out. If an LLM response can reach one of those, anyone who can influence the prompt can influence your server or your users' browsers.
Second, the agent configuration itself is an instruction surface that almost nothing audits. SKILL.md files, Cursor rules, AGENTS.md and MCP configs are all executed by an agent. They can hide instructions in zero-width characters, tell the agent to read ~/.ssh or .env, or launch an unpinned npx -y some-server MCP process. vibe-shield has a separate audit-skills command that checks those files for 12 classes of problem.
The four false positives I fixed in v2.1.1
A security scanner with a high false-positive rate gets uninstalled within a week, so v2.1.1 was entirely about precision. These are the four changes, all driven by real-world snippets that the scanner got wrong:
-
Handler detection and auth recognition. Destructured arguments in an App Router handler could hide the real handler body. The scanner now extracts the body correctly and recognizes common auth helpers (such as
supabase.auth.getUser()), explicit 401 guards andwith*wrapper functions, without borrowing auth from a different handler in the same file. - IDOR scoping. Findings are now limited to ownership fields, predicates or tracked aliases that actually come from the request. A session-derived owner sitting near some unrelated request data no longer fires, and a narrow "mismatch, then return" guard on the same property is recognized.
-
UI components excluded from route checks. The admin/destructive-surface rule now only looks at server endpoints and server actions, so a button component called
DeleteButtonstops being flagged. - Server-side Supabase admin helpers. An admin helper that is not reachable from client code is legitimate, so it is no longer reported. Public-prefixed secrets and client-reachable service-role clients are still reported.
The trade-off is worth being upfront about. Recognizing a with* wrapper or an auth helper is a signal, not proof that the wrapper actually enforces anything, and the client import graph is best-effort rather than a full bundler simulation. A clean score means the rules did not fire, not that the app is secure, and you should still review manually and pair it with a deeper tool like CodeQL or Semgrep.
Try it
It runs locally, has zero runtime dependencies and does not send your code anywhere:
npx -y @vibefoundry/vibe-shield@latest scan .
Use the scoped package name. The unscoped vibe-shield on npm belongs to a different project. Here is the repo:
avivgerz-hue
/
vibe-shield
60-rule static security scanner, MCP server & skill poisoning auditor for AI-generated codebases (Claude Code, Codex, Cursor).
π‘οΈ vibe-shield
The security gate for AI-written code. Find exposed secrets, unguarded routes and missing RLS before you ship.
β If this tool helps your AI coding workflow, please give this repository a Star! It helps more developers discover the project.
npx -y @vibefoundry/vibe-shield@latest scan .
Illustrative two-file scan; fixes are applied manually. Results vary by project. A clean score is not a security guarantee.
VibeFoundry Agent Suite
- π¨ unslop β UI Design DNA & Anti-Slop Engine Β·
npx -y unslop-mcp@latest init - π‘οΈ vibe-shield β Security Scanner for AI Code Β·
npx -y @vibefoundry/vibe-shield@latest scan . - π» ghost-qa β Autonomous Headless Browser QA Β·
npx -y ghost-qa@latest test http://localhost:3000
vibe-shield is a static security scanner plus an Agent Skill that helps Claude Code, Codex, Cursor and Windsurf audit before they say βdoneβ.
v2.0.0: eight defensive vectors
The v2 catalogue covers client exposure; auth and route protection; database/RLS; AI andβ¦
It is MIT licensed and part of a small suite of tools I maintain for the same underlying problem (agents move fast and something has to keep the output honest). The siblings are unslop, a UI design linter that catches generic AI-generated interfaces, and ghost-qa, a headless-browser QA runner:
avivgerz-hue
/
unslop
Studio-grade UI design systems, Design DNA extractor & Anti-Slop MCP server for AI coding agents (Claude Code, Codex, Cursor, Windsurf).
unslop v2.1.0
β If this tool helps your AI coding workflow, please give this repository a Star! It helps more developers discover the project.
VibeFoundry Agent Suite: π¨ unslop β UI Design DNA & Anti-Slop Engine (npx -y unslop-mcp@latest init) Β· π‘οΈ vibe-shield β Security Scanner for AI Code (npx -y @vibefoundry/vibe-shield@latest scan .) Β· π» ghost-qa β Autonomous Headless Browser QA (npx -y ghost-qa@latest test http://localhost:3000)
npx -y unslop-mcp@latest init
Your agent can write the code. unslop gives it a measurable design engineering protocol.
unslop is a zero-runtime-dependency UI/UX Design Engineering CLI, native MCP Server, and AI Agent Skill. It audits 72 rules across seven design craft pillars, installs ten studio-grade archetypes, extracts Design DNA, exports four token formats, and turns every finding into an exact, prioritized transformation blueprint for JSX/TSX, HTML, Vue, Svelte, CSS, and Tailwind codebases.
Works natively with Claudeβ¦
avivgerz-hue
/
ghost-qa
Autonomous headless browser QA & self-healing verification loop for AI coding agents
π» ghost-qa
Autonomous headless browser QA & self-healing verification loop for coding agents. v2.1.0 β the 8-Trap Deep Browser Inspection Engine: 3 viewports Γ light/dark, QA Health Score (A+β¦F), interactive HTML dashboard, and regression diffing.
β If this tool helps your AI coding workflow, please give this repository a Star! It helps more developers discover the project.
π§° The VibeFoundry Agent Suite
One command each β star all three and keep the whole suite free & maintained:
- π¨ unslop β UI Design DNA & Anti-Slop Engine (
npx -y unslop-mcp@latest init) - π‘οΈ vibe-shield β Security Scanner for AI Code (
npx -y @vibefoundry/vibe-shield@latest scan .) - π» ghost-qa β Autonomous Headless Browser QA (
npx -y ghost-qa@latest test http://localhost:3000)
npx -y ghost-qa@latest test http://localhost:3000
Coding agents (Claude Code, Codex, Cursor, Windsurf) write code blind β then declare victory while the actual browser has React hydration errors, broken buttonsβ¦
If you have seen an agent-written vulnerability pattern that I have not covered, or a false positive the scanner produces on your code, please open an issue or tell me in the comments. Those reports are the most useful thing anyone can send me.
Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes β full credit and traffic to the original publisher.