TL;DR: This is a guide to reviewing your own code, in your own repository, for security problems — not a guide to finding or exploiting vulnerabilities in anything you don't own. A defensive security review prompt names a trust boundary, checks specific risk categories, and forbids exploit code. Its output is a lead list for a human to verify, not a clearance, because it cannot see your secrets manager, network rules, or actual deployment.
This is a defensive guide, and that's a real constraint, not a disclaimer
Every prompt in this post is built for one situation: you have code you're authorized to review, in a repository you own or work on, and you want an AI's help finding problems before they ship. That's the entire scope. None of it is written to help anyone probe, test, or exploit a system they don't have explicit permission to touch — and where a prompt could plausibly be pointed the other way, it says so.
That framing isn't a legal disclaimer bolted onto the top. It shapes what the prompts actually ask for. Every template below asks the model to describe an attack path in plain language and name the fix. None of them ask for working exploit code, a payload, or a proof-of-concept script — because the moment a prompt starts optimizing for "prove this is exploitable," it stops being a code review and becomes something that needs a different kind of authorization entirely.
How is a security review different from a general code review?
Live posts on this site already cover general-purpose code review prompting in depth: how to prompt for a genuinely useful code review covers severity calibration and evidence structure, and the code review prompt generator builds a parameterized prompt for correctness, readability, and maintainability. Security is one line item in a general review. Here it's the entire pass, and that changes the structure of the prompt in three ways.
First, a general review asks "is this code good." A security review asks a narrower question: "what can an attacker who controls X do to this code." That requires you to name X, the trust boundary, before you write a single line of the prompt.
Second, a general review can afford to be somewhat exploratory. A security review works best scoped to specific categories, because "check for security issues" produces the same shallow, generic pass that "review this code" produces for correctness. Naming the categories is what turns a vague request into a targeted one.
Third, the fix in a general review is usually "change this line." The fix in a security review often has to include how you'd confirm it actually closes the gap, because a plausible-looking fix for an injection or an access-control bug can still leave the underlying problem in place.
Here's a small worked example of why the trust boundary has to come first. Take a function that lets a caller delete a record by ID, with no ownership check beyond confirming the caller is logged in. Reviewed as a general code-quality question, that function is fine: it's short, it does what its name says, and nothing about it looks wrong in isolation. Reviewed as a security question against a stated trust boundary, "any logged-in user, not just the record's owner," the exact same eight lines are a broken-access-control bug with a concrete attacker capability: any account can delete any other account's data. The code never changed. The question you asked it did.
Same code, different verdict depending on who can reach it
That worked example generalizes into a habit worth building before you write a single review prompt: ask who can reach this code, not just what the code does. An admin-only internal tool and a public-facing endpoint can share the exact same handler, the exact same input parsing, and the exact same database call, and still deserve completely different verdicts.
If the caller is a fixed set of employees behind an internal network and an SSO login, a missing rate limit on a search endpoint is a minor finding, maybe not worth a fix this sprint. If the same handler is reachable from the public internet with only a session cookie between it and anyone who signs up, the missing rate limit is a real path to credential-stuffing or scraping abuse. Neither verdict is about the code being different. Both are about who the trust boundary actually admits, which is exactly why the scoping prompt above asks for "who can reach it" before asking for a single finding.
What security categories should you actually check?
Rather than inventing a category list, anchor the review to a maintained, public one. OWASP's Top 10:2025 — the project's current list, at top10.owasp.org/2025 — is the broadly recognized reference for web application risk categories, and using it means your review prompt is checking against categories a security engineer would also recognize, not a private taxonomy you made up for one diff.
| OWASP Top 10:2025 category | What to check for in your own diff |
|---|---|
| Broken Access Control | Does every handler check authorization, not just that a user is logged in, before touching a specific record? |
| Security Misconfiguration | Are defaults (CORS, debug flags, verbose errors) safe for how this code actually ships? |
| Software Supply Chain Failures | Does a new or updated dependency need a deliberate look, not just a version bump? |
| Cryptographic Failures | Is sensitive data encrypted at rest and in transit with a current algorithm, not a legacy one kept for compatibility? |
| Injection | Does attacker-controlled input ever reach a query, shell command, template, or deserializer unescaped? |
| Insecure Design | Does the design itself assume trust it shouldn't, independent of any single implementation bug? |
| Authentication Failures | Are session and credential handling resistant to the obvious bypasses (weak reset flows, missing rate limits)? |
| Software or Data Integrity Failures | Does anything trust an update, a plugin, or a deserialized object without verifying where it came from? |
| Security Logging and Alerting Failures | Would this code's failure path actually get noticed, or does it fail silently? |
| Mishandling of Exceptional Conditions | Do error paths fail closed (deny by default) rather than open? |
Not every diff touches all ten. Scoping your prompt to the two or three categories your change actually implicates produces a sharper review than asking for all ten every time.
How do you scope a review before you start prompting?
Run a short scoping pass first, before the review itself. This is the step most security-review prompts skip, and it's the difference between a review that finds what matters and one that produces a generic list.
Role: You're helping me scope a security review of my own code, in my own
repository. This is preparation, not the review itself.
Context
- Code: [paste the file or module]
- What it does: [one sentence]
- Who can reach it: [public internet, authenticated users only, internal
service, local only]
Task
Identify, for this code specifically:
1. Every entry point where external input reaches this code.
2. The trust boundary at each entry point — what's attacker-controlled
versus what's already validated by something upstream.
3. What sensitive data (credentials, PII, payment info, internal state)
this code touches, reads, or writes.
4. Which 2-3 categories from OWASP's Top 10:2025 are actually relevant
here, and why the others aren't.
Output: a short scope, not a findings list yet.
That scope becomes the input to the actual review prompt, which is why it's worth running separately rather than folding into one giant request.
What does a full defensive review prompt look like?
With a scope in hand, the review prompt itself can be specific instead of generic. This one is deliberately built with the anti-exploit constraint stated up front, not implied.
Role: Application security reviewer. You review code you have explicit
permission to review, and this is that code. You never produce working
exploit code, attack payloads, or proof-of-concept scripts — only a
description of the attack path and the fix.
Context
- Code: [paste it]
- Scope from the prior step: [entry points, trust boundary, sensitive data]
- Categories to check: [the 2-3 you scoped, e.g. injection, access control]
- Language/framework: [and version]
Task
Review for the scoped categories only. Do not expand into unrelated areas
unless you find something that clearly belongs in this diff.
Rules
- For each finding: the specific line or block, what an attacker with the
access described in the trust boundary gains, and the fix.
- Separate authentication from authorization explicitly. Being logged in
and being allowed to touch this specific record are different checks,
and conflating them is the most common real bug in this category.
- If a finding depends on something you can't see from this code alone
(how a secret is stored at runtime, whether a firewall rule blocks a
path, whether this is actually reachable from the internet), say so
explicitly instead of guessing.
- Do not produce exploit code, a payload, or a working proof of concept.
Describe the attack path in plain language instead.
- Say when a category checked out clean. An empty result for a category
you scoped is useful information, not a failure to find something.
Output
Findings grouped by category, each with line, attacker capability, and fix
· an explicit list of anything you could not assess without runtime or
deployment information.
That second-to-last rule in the constraints, the one requiring the model to name what it can't see, is doing more work than it looks like. It's the difference between a model that quietly guesses at your infrastructure and one that tells you where its view actually ends.
What can't an AI security review see, no matter how good the prompt is?
This is the part worth being blunt about, because it's easy to read a well-structured finding and assume it's a complete picture. It isn't, and the gap is structural, not a matter of prompting harder.
| Feature | Visible from pasted code | Not visible without deployment context |
|---|---|---|
| Whether input is validated before reaching a sink | ||
| Whether a secret is hardcoded in this file | ||
| How that secret is actually stored and rotated in production | ||
| Whether an endpoint is reachable from the internet at all | ||
| Firewall, VPC, and network segmentation rules | ||
| IAM roles and least-privilege enforcement | ||
| Whether logging/alerting actually fires in the live environment | ||
| Real traffic patterns and rate-limiting behavior under load |
A model can correctly flag that a function reads process.env.API_KEY directly, because that's visible in the text. It cannot tell you whether that variable is actually populated from a secrets manager with rotation and access logging, or copy-pasted into a .env file sitting in a build context — because that fact lives in your infrastructure, not your source file. Both hardcoded-looking patterns produce the same code-level finding and require completely different follow-up.
The same gap shows up on the network side. A model reading a route handler can tell you the handler has no authentication check. It cannot tell you whether that handler sits behind a load balancer that already enforces auth at the edge, whether it's only ever reachable from an internal VPC that never sees public traffic, or whether it's the one route that genuinely is exposed to the entire internet. All three are the identical finding at the code level and wildly different findings at the risk level, and only someone who knows the actual deployment can tell them apart.
How do you verify a finding before you act on it?
Three checks, run by a person, before any finding changes code:
- Confirm the attacker capability claim. If the finding says "an attacker with X access gets Y," verify X is actually reachable given your real deployment, not just plausible given the code alone.
- Reproduce the trigger condition, even loosely. A finding you can't restate as "if this specific input arrives, this specific bad thing happens" is closer to a hunch than a bug.
- Check whether the fix actually closes the gap, not just the symptom. A parameterized query fixes one injection path; it says nothing about whether the same tainted value reaches a different sink three functions later.
Walk one finding through all three and the value of the loop is easier to see than it is in the abstract. Say the model flags: "any authenticated user can fetch any other user's invoice by ID, because the handler checks the session but never checks the invoice's owner." Step one confirms authenticated users really do reach this route in production, not just in a local dev server with auth disabled. Step two means actually calling the endpoint with a second test account's credentials and a first account's invoice ID, to see the record come back, rather than trusting the description. Step three means checking the applied fix compares the invoice's owner field against the session's user ID specifically, not just adding a broader "is admin" check that happens to also stop this one test case while leaving the underlying missing-ownership-check pattern in every other handler that copied the same original code.
If a finding touches how code integrates a language model rather than a traditional backend, add a fourth check: whether untrusted text (a user message, a scraped page, a tool result) can reach the model's instructions and redirect its behavior. That's a distinct risk category from the ten above, and it's covered in depth in prompt injection attacks: how to protect your AI app — worth a dedicated pass if the code you're reviewing has an LLM anywhere in its request path, the same way prompt injection deserves its own scoped review rather than a bullet point buried in a general one.
This verification loop is also a good candidate for a second, independent model pass rather than trusting a single result. Comparing two takes on the same finding, or running the same review prompt twice and checking whether the verdict holds, is the same underlying idea covered in LLM-as-judge: using AI to evaluate AI output — one pass from a probabilistic reviewer is a sample, not a certainty, whether it's grading a chatbot answer or flagging a security issue.
When does this stop being enough?
Be honest with yourself about where a prompt-based review tops out. It's a fast, cheap first pass over the code you can paste into a context window. It is not a dedicated static-analysis scanner built to run on every commit without a human retyping a prompt each time, and it is not a penetration test, a compliance audit, or a substitute for a professional security review on anything that handles money, credentials, or regulated data at real scale. AI code review vs human review: what each catches covers the accuracy side of that gap in more depth, including documented false-positive rates, if you need the numbers behind this caution rather than just the caution itself.
Prompt Architects itself doesn't change that boundary. It's a prompt-enhancement and prompt-library layer, not a scanner: it doesn't read your repository, run in your CI pipeline, or post findings to a pull request. What it does is keep a review prompt like the ones above structured, versioned, and reachable from Claude Code, Cursor, or Codex CLI, so the scoping questions and the anti-exploit constraint survive being reused, instead of getting rewritten from memory, and slightly weaker, every time.
Stop rewriting prompts. Start shipping.
Works with ChatGPT, Claude, Gemini, Grok, Midjourney, Ideogram, Veo3 & Kling. 4.8★ on the Chrome Web Store.
Create An Account