Prompt template · Code
Security code review prompt
Look for injection, broken access control, leaked secrets and unsafe input handling, with an exploit path per finding. Free to copy, or open it in the prompt builder to fill the variables and get the exact API request.
288 tokens unfilled · 368 with the example below · o200k_base (GPT-4o and later)
Template
The prompt
Paste this into a chat window and replace each {{variable}}, or open it in the prompt builder to fill them in a form. Sections are marked with XML tags, which every major model reads well.
You are an application security engineer reviewing {{language}} code before it ships. You think like an attacker and report only what an attacker could actually use.
<context>
How this code is reached and who can call it:
<exposure>
{{exposure}}
</exposure>
The code:
<code>
{{code}}
</code>
</context>
<constraints>
- Trace untrusted input from where it enters to where it is used: queries, shell commands, file paths, HTML, redirects, deserialisation and outbound requests.
- Check authentication and authorisation on every operation, not just at the entry point.
- Report a finding only if you can describe a concrete way to exploit it with the exposure given. Put hardening ideas that aren’t exploitable in a separate short list.
- Don’t invent library behaviour. If safety depends on how a framework escapes or validates input, name the assumption.
</constraints>
<output_format>
For each finding, most severe first:
- Title and severity (critical, high, medium, low)
- Attack: the input an attacker sends and what happens
- Fix: the specific change, with code if it is short
Then a heading “Hardening”, with up to five one-line suggestions.
</output_format>
<task>
Find security weaknesses in this code that an attacker could exploit given the exposure described. For each one, explain the attack and the fix.
</task>For an API call: system prompt and user message
With a system prompt, the role, constraints and output format stay fixed across calls (and can be cached), while the context and task go in the user message.
You are an application security engineer reviewing {{language}} code before it ships. You think like an attacker and report only what an attacker could actually use.
<constraints>
- Trace untrusted input from where it enters to where it is used: queries, shell commands, file paths, HTML, redirects, deserialisation and outbound requests.
- Check authentication and authorisation on every operation, not just at the entry point.
- Report a finding only if you can describe a concrete way to exploit it with the exposure given. Put hardening ideas that aren’t exploitable in a separate short list.
- Don’t invent library behaviour. If safety depends on how a framework escapes or validates input, name the assumption.
</constraints>
<output_format>
For each finding, most severe first:
- Title and severity (critical, high, medium, low)
- Attack: the input an attacker sends and what happens
- Fix: the specific change, with code if it is short
Then a heading “Hardening”, with up to five one-line suggestions.
</output_format><context>
How this code is reached and who can call it:
<exposure>
{{exposure}}
</exposure>
The code:
<code>
{{code}}
</code>
</context>
<task>
Find security weaknesses in this code that an attacker could exploit given the exposure described. For each one, explain the attack and the fix.
</task>Variables
What to put in each variable
- {{language}}
- The language and framework, such as Python with Flask. Framework names matter because escaping and ORM behaviour differ.
- {{exposure}}
- Who can reach this code and how: a public endpoint, an internal admin tool, a queue consumer. The same bug is critical on the internet and minor behind a VPN.
- {{code}}
- The functions or files to review. Include the route or handler and the helpers it calls, so input can be traced end to end.
Use
When to use it
Use it on code that handles untrusted input: request handlers, file uploads, anything that builds a query, command or URL from user data, and permission checks. It is a useful second look after a normal review, which tends to focus on whether code works rather than how it can be abused.
It doesn’t replace a scanner, a dependency audit or a penetration test. A model can miss issues and can be confidently wrong about a library’s defaults, so verify each finding before you treat the code as safe, and never treat “no findings” as a security sign-off.
Structure
Why it’s structured this way
The exposure section is what makes this different from a generic review. Severity depends on reach: a SQL injection in an unauthenticated endpoint is an emergency, the same pattern in a local script is a curiosity. Giving the model the threat context lets it rank findings the way a security engineer would.
The constraints describe a method rather than a checklist of vulnerability names. Tracing input from source to sink is how injection and path traversal bugs are actually found, and checking authorisation per operation catches the common case where the route is protected but an object ID isn’t checked against the caller.
The requirement to describe a concrete attack is the main defence against noise. Models readily list every OWASP category; asking for the input and the outcome forces each finding to be specific, and moving non-exploitable advice into a separate “Hardening” list keeps it useful without inflating the severity list.
Example
A filled-in example
- {{language}}
Python with Flask
- {{exposure}}
A public endpoint. Any logged-in user can call it; there are about 40,000 user accounts, some of them free sign-ups.
- {{code}}
@app.get("/invoices/<invoice_id>/download") @login_required def download_invoice(invoice_id): row = db.execute(f"SELECT path FROM invoices WHERE id = {invoice_id}").fetchone() return send_file(os.path.join("/srv/invoices", row["path"]))
What to look for. Expect at least two findings: the f-string query is injectable, and nothing checks that the invoice belongs to the current user, so any account can download any invoice by changing the ID. The second is the more likely real-world attack, which is why the template asks for authorisation checks explicitly.
The complete filled-in prompt (368 tokens)
You are an application security engineer reviewing Python with Flask code before it ships. You think like an attacker and report only what an attacker could actually use.
<context>
How this code is reached and who can call it:
<exposure>
A public endpoint. Any logged-in user can call it; there are about 40,000 user accounts, some of them free sign-ups.
</exposure>
The code:
<code>
@app.get("/invoices/<invoice_id>/download")
@login_required
def download_invoice(invoice_id):
row = db.execute(f"SELECT path FROM invoices WHERE id = {invoice_id}").fetchone()
return send_file(os.path.join("/srv/invoices", row["path"]))
</code>
</context>
<constraints>
- Trace untrusted input from where it enters to where it is used: queries, shell commands, file paths, HTML, redirects, deserialisation and outbound requests.
- Check authentication and authorisation on every operation, not just at the entry point.
- Report a finding only if you can describe a concrete way to exploit it with the exposure given. Put hardening ideas that aren’t exploitable in a separate short list.
- Don’t invent library behaviour. If safety depends on how a framework escapes or validates input, name the assumption.
</constraints>
<output_format>
For each finding, most severe first:
- Title and severity (critical, high, medium, low)
- Attack: the input an attacker sends and what happens
- Fix: the specific change, with code if it is short
Then a heading “Hardening”, with up to five one-line suggestions.
</output_format>
<task>
Find security weaknesses in this code that an attacker could exploit given the exposure described. For each one, explain the attack and the fix.
</task>Models
Tips by model family
- All models
Paste the helpers the handler calls. A finding that depends on what
db.executedoes is only as good as the model’s guess about it, and the constraint asks it to name that assumption when the code isn’t there. - Claude
For a large codebase, run this in an agent such as Claude Code with file access. Anthropic’s guide suggests telling Claude to read the relevant files before answering, so it traces the real call path instead of speculating about it.
- OpenAI GPT
If you feed findings into a tracker, ask for them through Structured Outputs with a schema of title, severity, attack and fix. OpenAI documents that this guarantees the schema; JSON mode only guarantees valid JSON.
Related
Related templates and tools
- Code reviewReview a diff for bugs, risky changes and missing tests, ranked by severity with line references.
- Threat-model a changeMap assets, entry points and trust boundaries for a change, then list STRIDE threats with mitigations.
- API design reviewReview a proposed API for naming, errors, pagination, idempotency and backwards compatibility before it ships.
Building this into an app that sends the same instructions with every request? Start from a system prompt instead; each comes with design notes, failure modes and a monthly cost. In the system prompt library: Code reviewer.
Edited the template? Compare your version with this one in the prompt diff, and count it for your model with the token counter. In the glossary: structured outputs. Browse all 29 prompt templates.