10 Copy-Paste Security Prompts for Your AI Agent

Each prompt is engineered for a coding agent. Explicit scope, regex patterns, output format specs, edge case handling. Paste one at Cursor, Lovable, or Claude Code and let it do the actual security work.

How to use this post.

Each prompt has three parts. The question (your worry, in plain English). The goal (what success looks like when the prompt finishes). The directive (the actual block to paste at your agent).

Open Cursor, Lovable, Bolt, or Claude Code. Pick the prompt that matches your worry. Paste the directive. Let the agent work.

If you haven't read the field guide that explains why each of these matters, start with: You Vibe-Coded an App. Now Don't Get Pwned.

Prompt 1: Find every exposed secret in my code.

Question: are any of my API keys, tokens, or secrets sitting in my code where a bot can find them in under four minutes?

Goal: a complete inventory of every potential secret in the codebase, where it lives, whether it's safe, and the exact action to take for each one.

Prompt 1
Audit this repository for exposed secrets. Read-only; do not modify files. Scope: tracked files in working tree, untracked files not in .gitignore, last 100 commits of git history (use `git log --all -p -S<pattern>`). Include source, config, env files, dotfiles, JSON/YAML, markdown, shell. Exclude node_modules, .next, dist, build, .git, vendored libs, lockfiles.

Patterns (regex): OpenAI `sk-(?:proj-)?[A-Za-z0-9_-]{20,}`. Anthropic `sk-ant-[A-Za-z0-9_-]{20,}`. Stripe live `(sk|rk|pk)_live_[A-Za-z0-9]{24,}`. Stripe test `(sk|rk|pk)_test_[A-Za-z0-9]{24,}`. AWS access `(AKIA|ASIA)[A-Z0-9]{16}`. AWS secret `(?<![A-Za-z0-9])[A-Za-z0-9/+=]{40}(?![A-Za-z0-9])` (only flag near 'aws','secret','AKIA'). GitHub token `gh[pousr]_[A-Za-z0-9]{36,}`. Google API `AIza[0-9A-Za-z\-_]{35}`. JWT `eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+`. Supabase service role: JWTs near "service_role" or "SUPABASE_SERVICE_ROLE_KEY". Generic high-entropy: string literals 32+ chars matching `[A-Za-z0-9+/=_-]{32,}`, excluding base64 images, hash digests, lockfile integrity fields.

Output as markdown table per match: File, Line, Pattern, Redacted Value, In History?, In .gitignore?, Severity, Action. Redaction: first 6 + last 4 chars only; never paste full secret. If in history but not working tree, mark "history only" (still critical). Severity: CRITICAL is live production keys (sk_live_, AKIA, service_role) in working tree or history. HIGH is test keys, expired-looking patterns, or generic high-entropy clearly a credential. MEDIUM is ambiguous high-entropy near words like "password", "token", "secret".

Action template: CRITICAL/HIGH "Rotate at [provider URL]. Move to env var [SUGGESTED_NAME]. Scrub from history with git-filter-repo." MEDIUM "Investigate. Likely should be env var if real credential." End report with: counts by severity; whether `.env`, `.env.local`, `.env.production` are in `.gitignore`; client-side env vars in use (VITE_, NEXT_PUBLIC_, REACT_APP_, EXPO_PUBLIC_) flagged each as "Shipped to browser. NOT private."; whether to run gitleaks/trufflehog for deeper scan. If uncertain about a match, include and flag for human review.

Prompt 2: Add Row Level Security to every Supabase table.

Question: can a stranger with my anon key read my entire database right now?

Goal: every table has RLS enabled with policies that match its real-world ownership model. Nothing breaks for legitimate users.

Prompt 2
Add Row Level Security to every Supabase table. Wrong policies can lock users out. Follow this exact procedure.

Step 1: inventory tables in `public` schema. Use Supabase MCP if available, else read migrations/SQL files. Output: Table, Has user_id-style FK?, RLS enabled?, Existing policies.

Step 2: classify each table. USER_OWNED: column ties row to auth.users (user_id, owner_id, profile_id, created_by). PUBLIC_REF: shared reference data (countries, products catalog). TENANT_OWNED: has tenant_id/org_id/workspace_id. ADMIN_ONLY: never client-accessible (audit logs, system config). AMBIGUOUS: STOP. Ask before generating policies.

Step 3: for non-ambiguous tables, generate SQL. `ALTER TABLE ... ENABLE ROW LEVEL SECURITY`. USER_OWNED: SELECT `auth.uid() = user_id`; INSERT `with check (auth.uid() = user_id)`; UPDATE `auth.uid() = user_id with check (auth.uid() = user_id)`; DELETE `auth.uid() = user_id`. PUBLIC_REF: SELECT `to authenticated, true`; writes have no public policy (service role only). TENANT_OWNED: use helper `is_member_of(tenant_id uuid)` checking memberships table; generate it if missing; all CRUD use is_member_of(tenant_id). ADMIN_ONLY: no policies for anon/authenticated; document server-side service role only.

Step 4: write migration to `supabase/migrations/<timestamp>_enable_rls.sql`. Do NOT execute.

Step 5: for each table, generate ONE example query that should succeed and ONE that should fail. Output as markdown checklist for post-migration verification.

Step 6: final summary. Tables RLS-enabled: N. Policies created: M. AMBIGUOUS tables needing my decision: list each. Tables with existing policies: do NOT overwrite; output diff for my review.

Hard rules. Never disable an existing policy without explicit confirmation. Never `USING (true)` on USER_OWNED or TENANT_OWNED. That grants global read. No obvious user_id FK = AMBIGUOUS. Don't guess.

Prompt 3: Move every hardcoded secret into proper environment variables.

Question: are my secrets where they should be, in env vars, not in my code?

Goal: zero hardcoded secrets, a clean .env.local, a safe-to-commit .env.example, and a list of every var I need to set in my hosting dashboard.

Prompt 3
Migrate hardcoded secrets and config out of source code into env vars. When in doubt, ask.

Step 1: detect framework + runtime. Check package.json scripts, next.config.*, vite.config.*, remix.config.*, astro.config.*, app.json (Expo). Output: detected framework, env var prefix convention, server-vs-client split.

Step 2: find every value that should be an env var. API keys/tokens/secrets (patterns from Prompt 1). Database connection strings. Webhook secrets. OAuth client IDs/secrets. Third-party URLs that change between environments. Feature flags differing by environment.

Step 3: classify each value. SERVER_ONLY: grants write access, costs money, or authorizes actions. No public prefix. CLIENT_SAFE: must reach browser (public Stripe key, Supabase URL/anon key, Maps key). Use framework's public prefix.

Step 4: for each value: new env var name (SCREAMING_SNAKE_CASE, descriptive, framework-conventional); code change as before/after diff; goes in .env.local, .env.example, or both.

Step 5: file changes. `.env.local`: actual secrets (gitignored). `.env.example`: same keys, blank/placeholder values (committed). `.gitignore`: add `.env`, `.env.local`, `.env.*.local`. Replace hardcoded refs with env var lookups using framework idiom.

Step 6: deployment checklist. Table of every var: classification, where to set (Vercel/Netlify/Railway/Fly). Specific dashboard URL paths if host detectable from config. Vars differing between production vs preview.

Flag LOUDLY: public-prefixed vars that look like server secrets (NEXT_PUBLIC_STRIPE_SECRET_KEY) leaking to browser RIGHT NOW; values committed to git history (cross-ref Prompt 1); framework gotchas (Vite needs VITE_; Next.js client components need NEXT_PUBLIC_; server actions don't). Do NOT delete original hardcoded values until I confirm new env vars work locally.

Prompt 4: Scrub leaked secrets from git history permanently.

Question: I leaked a key to git. How do I make sure it's actually gone, from every commit, every fork, every CI cache?

Goal: the leaked secret is unrecoverable from anywhere I control, a new secret is in place, and I have a checklist for the places I might NOT control.

Prompt 4
Walk me through scrubbing a committed secret from git history. Order matters.

Inputs: secret-bearing file path: [PATH]. Provider: [PROVIDER].

Step 1, pre-flight (do not skip). Tell me to revoke the secret at [PROVIDER's dashboard URL] and confirm I've done it before continuing. Working tree must be clean (`git status` empty); if not, tell me to commit or stash. Backup the repo: `cp -r repo repo-backup`.

Step 2, scrub with git-filter-repo (NOT BFG; git-filter-repo is GitHub's current recommendation). Show install command for my OS. `git filter-repo --replace-text` invocation that replaces secret with `***REMOVED***` everywhere. If file should be deleted entirely: `git filter-repo --path <path> --invert-paths`. Explain each flag in one short line.

Step 3, verify. `git log --all -p -S'<first 8 chars>'`: confirm zero matches. `git reflog expire --expire=now --all && git gc --prune=now --aggressive`.

Step 4, remote. Warn: this rewrites history; anyone with a clone must re-clone. `git push --force-with-lease origin --all && git push --force-with-lease origin --tags`. Tell me to notify collaborators they MUST re-clone, not pull.

Step 5, hunt the copies (most guides skip this). GitHub Actions/CI logs. GitHub forks (search api.github.com/repos/.../forks; ask each fork owner). Vercel/Netlify build logs. Sentry/observability tools that may have logged it. Slack/Discord/email where I might have shared it. Local clones on other machines. Provider's usage logs (was it used while exposed?).

Step 6, post-checklist. Old secret revoked. New secret generated, stored in env vars. App redeployed. History scrubbed locally. Force-pushed. Collaborators notified. CI/build logs cleared. Provider usage logs reviewed. If unauthorized activity: support ticket opened.

Reminder: scrubbing is necessary but NOT sufficient. The secret MUST be rotated with the provider FIRST. Otherwise bots may already have it.

Prompt 5: Add rate limiting to every API route.

Question: can someone hammer my expensive endpoints and bankrupt me?

Goal: every API route has a rate limit appropriate to its cost class. Abuse returns a clean 429 with retry-after.

Prompt 5
Add rate limiting to every API route. Use Upstash Ratelimit + Upstash Redis (free tier covers most apps) unless I say otherwise.

Step 1: detect framework + routes. Next.js App Router: `app/**/route.{ts,js}`. Next.js Pages: `pages/api/**/*`. Express/Fastify/Hono: route registrations. Server Actions: `app/**` with "use server". Output: every route with path, method, file.

Step 2: classify. HIGH_COST: paid LLM API (OpenAI, Anthropic, Replicate, ElevenLabs); emails/SMS; expensive third-party. MEDIUM_COST: DB writes, file uploads, complex queries. LOW_COST: simple reads, public endpoints, health checks. AUTH_SENSITIVE: login, signup, password reset (limit by IP, NOT user). Default limits: HIGH_COST 10/min/user + 30/min/IP. MEDIUM_COST 60/min/user. LOW_COST 300/min/IP. AUTH_SENSITIVE 5/min/IP + 10/hour/IP. Override if I say.

Step 3: implementation. Install `@upstash/ratelimit @upstash/redis`. Shared limiter file (`lib/ratelimit.ts`) with one limiter per cost class, sliding-window strategy. Insert limiter at top of each handler, before any expensive work. Identifier: user ID from session if available, else IP from `x-forwarded-for` (fallback: direct IP). On limit: 429 with headers `Retry-After`, `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`. Body: `{"error":"rate_limit_exceeded","retryAfter":<seconds>}`.

Step 4: env vars. `UPSTASH_REDIS_REST_URL`, `UPSTASH_REDIS_REST_TOKEN`. Add to `.env.local` + `.env.example`. Note in deployment checklist.

Step 5: observability. Log every 429 with route, identifier, limit class. If Sentry/Logtail/Axiom present, route logs there.

Step 6: test plan. Per cost class, one curl one-liner that exercises the limit.

Do NOT change route logic beyond inserting the rate-limit check. Cost class unclear? Ask before classifying.

Prompt 6: Add input validation to every endpoint.

Question: is my app trusting whatever garbage a user sends it?

Goal: every endpoint validates input against a strict schema before any business logic runs. No raw req.body access anywhere.

Prompt 6
Add strict input validation to every endpoint accepting user input. Use Zod.

Step 1: find every input entrypoint. API routes (App Router, Pages Router, Express, Fastify, Hono). Server actions. tRPC procedures. WebSocket message handlers. Webhook handlers (signature-verified webhooks like Stripe still need body validation). For each: what data it currently accepts (req.body, formData, query params, route params, headers).

Step 2: define a Zod schema per endpoint. Every expected field with correct type. Reasonable bounds: string min/max, number min/max, array length caps. `.email()`, `.url()`, `.uuid()`, `.datetime()` where applicable. `.strict()` so unknown fields are REJECTED, not dropped. Reject empty required fields explicitly.

Step 3: replace direct input access. Before: `const { name, email } = await req.json()`. After: `const parsed = MySchema.safeParse(await req.json()); if (!parsed.success) return Response.json({ error: "invalid_input", details: parsed.error.flatten() }, { status: 400 }); const { name, email } = parsed.data`.

Step 4: FLAG any endpoint reading authorization-controlling fields from request body. user_id, profile_id, owner_id from req.body instead of session. role, isAdmin, tenantId from req.body. price, amount, total accepted from client without server-side recompute. Output these in a separate section "AUTHORIZATION-VIA-INPUT BUGS". Validation alone won't fix them. They need to read from authenticated session, not body. Show the fix.

Step 5: file uploads also validate. File type (magic-byte check, not MIME header). File size cap. Images: dimensions sanity check. PDFs: don't trust filename extensions.

Step 6: output. List of schemas created (file paths). List of endpoints validated. "AUTHORIZATION-VIA-INPUT BUGS" section if any. Test plan: one VALID and one INVALID payload per endpoint, in curl form.

Never strip strictness to "make it work." Legitimate request rejected = schema is wrong. Fix the schema. Don't relax it.

Prompt 7: Audit my login flow for vulnerabilities.

Question: did my AI ship me an auth flow that someone can trivially break?

Goal: a complete report of every weakness in the auth flow, with file:line locations and the exact fix for each.

Prompt 7
Security-audit this app's authentication flow. Read-only.

Detect the auth stack first. Library: Supabase Auth, Clerk, Auth0, Firebase Auth, NextAuth/Auth.js, Lucia, custom? Token storage: cookie, localStorage, sessionStorage? Session verification: middleware, per-route, client-side only?

For every finding output: severity (CRITICAL/HIGH/MEDIUM), file:line, why it's a problem, exact fix.

CRITICAL: tokens in localStorage/sessionStorage (vulnerable to XSS exfiltration; should be httpOnly cookies). Session verification only frontend (attackers bypass with one fetch; server must verify every request). Privileged routes without server-side session checks (admin panel, billing, account deletion). Routes reading `user_id`/`role`/`isAdmin` from request body for authorization. Custom JWT signing/verification with missing/weak/hardcoded secret. Password reset that doesn't expire tokens, doesn't rate-limit, or sends token in URL parameter. OAuth callbacks not validating `state` (CSRF on OAuth). "Remember me" using long-lived non-rotating tokens.

HIGH: email/password change without re-authentication. Account enumeration via login error messages ("user not found" vs "wrong password"). Missing CSRF protection on cookie-auth state-changing endpoints (POST/PUT/DELETE). Sessions that don't rotate on privilege change (e.g., promoted to admin while logged in). Missing rate limits on /login, /signup, /reset-password (use Prompt 5's AUTH_SENSITIVE). Logout that only clears client state (server must invalidate session too).

MEDIUM: password requirements weaker than 12 chars or no breach-list check. No MFA option for admin users. Sensitive operations (delete account, change email) without email confirmation. Session cookies missing Secure, HttpOnly, SameSite=Lax/Strict. CORS allowing credentials AND wildcard origins.

Output: markdown table | Severity | Check # | File:Line | Description | Fix |. End with one-line verdict: "GO" or "NO-GO" + gating findings. Do not modify code.

Prompt 8: Set up Stripe webhook signature verification.

Question: can someone fake a "this user paid" webhook to my app and unlock paid features for free?

Goal: every webhook endpoint verifies signatures. Stripe events get processed exactly once. Replay attacks fail.

Prompt 8
Secure Stripe webhook integration. Other webhook handlers (Slack, GitHub, custom) get the same treatment.

Step 1: find every webhook receiver. Stripe POST routes (api/webhooks/stripe, api/stripe/webhook). Other webhooks (Slack signing, GitHub HMAC, Twilio): list each + current verification status. Output: Service, Endpoint, Verifies signature?

Step 2: fix Stripe endpoint in this order. (a) Raw body. Stripe verification needs exact raw bytes. Next.js App Router: `await request.text()`, NOT `request.json()`. Next.js Pages Router: `export const config = { api: { bodyParser: false } }`; read with stream. Express: `express.raw({ type: 'application/json' })` only on webhook route. Show framework-specific fix as diff.

(b) Signature verification. `stripe.webhooks.constructEvent(rawBody, signatureHeader, STRIPE_WEBHOOK_SECRET)`. try/catch. Failure = 400 immediately. Do NOT process. `STRIPE_WEBHOOK_SECRET` from env (never hardcode); add to `.env.example`. Signature from header `stripe-signature`.

(c) Idempotency. Stripe retries non-2xx for 3 days. Don't double-process. Add `event_id` table (or use existing). INSERT...ON CONFLICT DO NOTHING with event.id as PK BEFORE processing. 0 rows affected = already processed. Return 200. Show migration (SQL/Prisma/Drizzle).

(d) Processing. Wrap in DB transaction. Return 200 only after commit. Unexpected error during processing: 500 (Stripe retries). Log event_id.

(e) Event type whitelist. Handle expected types only (checkout.session.completed, invoice.paid). Unhandled types: 200, no action. Don't error.

Step 3: env var checklist. `STRIPE_SECRET_KEY` (server-only). `STRIPE_WEBHOOK_SECRET` (per endpoint, separate from API key). `NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY` (client-safe; pk_live_/pk_test_). Webhook secret location: Stripe dashboard, Developers, Webhooks, click endpoint, Reveal signing secret.

Step 4: test. `stripe listen --forward-to localhost:3000/api/webhooks/stripe`. `stripe trigger checkout.session.completed`. Assert: signature verification works, replay rejected, unexpected event types return 200 no-op.

Step 5: FLAG any code reading payment status / subscription status / "isPaid" from CLIENT or request body. CRITICAL. Source of truth is database state set BY verified webhook. Never trust client to declare payment. For non-Stripe webhooks from step 1: same pattern (raw body, signature header, env var, idempotency).

Prompt 9: Find frontend-only access controls and move them to the backend.

Question: are any of my admin checks happening only in the browser, where anyone can flip a switch and walk in?

Goal: every authorization check has a backend equivalent. Frontend checks remain for UX only.

Prompt 9
Audit this app for authorization that happens only in the frontend. Frontend checks are bypassable in 5 seconds with browser DevTools. All are critical. Read-only.

Step 1: find every authorization check. Search for: `isAdmin`, `isOwner`, `hasRole`, `can*`, `role ===`, `permissions.includes`. `if (user.role)`, `if (user.tier)`, `if (user.subscription)`. Conditional rendering of admin UI / paid features / sensitive actions. Custom hooks: `useIsAdmin`, `useCanEdit`, `usePermission`. Route guards / HOCs / layout-level checks. For each: file:line, the check expression, what it gates.

Step 2: classify each. FRONTEND_ONLY: only browser-side; backend doesn't enforce. BACKEND_ONLY: backend enforces; no frontend check (fine but maybe poor UX). DEFENSE_IN_DEPTH: both (ideal).

Step 3: for every FRONTEND_ONLY, find the corresponding API route or DB query. Generate backend enforcement. Pick: (A) API route check: verify session server-side; read role/permissions from DB OR verified session token (NOT request body); return 403 if check fails BEFORE doing work. (B) Database RLS: for DB read/write, write or extend RLS policy enforcing the rule at DB level. Most robust: rule applies even if accessed via other paths. (C) Middleware: Next.js `middleware.ts` gating route segments. Generate matcher + role check.

Step 4: output as markdown table. File:Line, Frontend Check, Gates, Current Backend Status, Proposed Backend Fix, Code.

KEEP frontend checks (good UX). Just ensure backend enforces every one. CRITICAL flags: user_id/owner_id/tenant_id used in check coming from URL or request body, not session (horizontal privilege escalation); admin endpoint accepting `userId` parameter (verify restricted to actual admins, not any authenticated user). Do NOT modify code. Output audit + proposed fixes for my approval.

Prompt 10: Pre-flight security check before deployment.

Question: if I shipped this right now, would I show up in a horror story tomorrow?

Goal: a go/no-go report covering every category in this guide. Each item PASS / FAIL / N/A with file:line for every fail and a fix-or-acknowledge instruction.

Prompt 10
Final pre-deployment security check. One CRITICAL fail = NO-GO. Output markdown table: # | Category | Severity | Status | Detail | Fix |.

Secrets & env (use Prompt 1 if needed). 1. CRITICAL: no hardcoded secrets in source files. 2. CRITICAL: no secrets in last 100 commits of git history. 3. CRITICAL: `.gitignore` covers `.env`, `.env.local`, `.env.*.local`. 4. HIGH: every public-prefixed env var (NEXT_PUBLIC_, VITE_) is safe to expose to browser. 5. HIGH: `.env.example` exists, documents every required env var.

Database (use Prompt 2 if needed). 6. CRITICAL: RLS enabled on every Supabase/Firebase user-data table. 7. CRITICAL: RLS policies actually restrict access (no `USING (true)` on user-owned tables). 8. HIGH: service role / admin keys referenced ONLY in server-side code.

Auth (use Prompt 7 if needed). 9. CRITICAL: session tokens in httpOnly cookies, not localStorage/sessionStorage. 10. CRITICAL: server-side session verification on every protected route. 11. HIGH: no handlers reading `user_id`/`role` from request body for authorization. 12. HIGH: login, signup, password-reset endpoints rate-limited.

API safety (use Prompts 5+6 if needed). 13. CRITICAL: every endpoint accepting user input validates with Zod or equivalent. 14. CRITICAL: rate limiting on every endpoint calling a paid third-party API. 15. HIGH: CORS set to specific origin(s), not `*`, on routes accepting credentials.

Payments (use Prompt 8; mark N/A if no payments). 16. CRITICAL: Stripe webhook handlers verify `stripe-signature`. 17. CRITICAL: Stripe secret key (sk_*) used only server-side. 18. HIGH: webhook handlers idempotent on event ID.

Access control (use Prompt 9 if needed). 19. CRITICAL: every frontend authorization check has matching backend enforcement. 20. HIGH: no endpoint accepting `userId` parameter (which user to act on) except endpoints explicitly admin-only.

Infrastructure. 21. HIGH: production HTTPS only. 22. HIGH: cookie flags Secure, HttpOnly, SameSite=Lax (Strict for sensitive). 23. MEDIUM: Sentry/equivalent scrubs PII. 24. MEDIUM: no source maps in production OR uploaded privately to error monitoring.

Code hygiene. 25. MEDIUM: no `console.log` of secrets, tokens, or user PII in production. 26. MEDIUM: no TODO/FIXME/XXX referencing security gaps. 27. LOW: dependencies current.

Reporting. Every FAIL: file:line + concrete fix. Every N/A: explain why. Severity-weighted summary: # CRITICAL fails, # HIGH, # MEDIUM, # LOW. Final verdict on its own line: "GO ✅" or "NO-GO ❌, fix CRITICAL items first". Do NOT modify files. Read-only audit. Can't determine something with confidence? Mark FAIL, explain what would need to be true for PASS.

After the prompts run.

If everything came back clean, you're ahead of most apps shipping right now. Most app's defenders run their first security check after a real customer reports a problem.

If something came back red, fix the CRITICAL findings first. Then HIGH. MEDIUM and LOW can wait until tomorrow. The goal isn't a perfect security report. It's closing the door on the breach classes that actually take apps down.

Set the calendar reminder. Every Monday: cost dashboards, auth provider logs, GitHub Secret Scanning alerts, deploy logs, Have I Been Pwned. Five minutes. The compounding effect of those five minutes is the difference between not being a horror story and being one.

Run Composed on your last 10 PRs.

Apply for design partner access and we’ll show which findings are real, exploitable, and worth fixing — free.

Run it on 10 PRs →