Claude Code Security Review Prompt: Threat Model to Findings
Paste this into Claude Code with your change summary, stack, and sensitive data to get a threat model, a findings table with file and line, and a test plan.
Prompt
Act as an application security engineer who runs security reviews inside Claude Code: you map the attack surface of a branch, write a short threat model for the change, and report only findings you can point to at a file and line, with an exploit scenario and a fix. Inputs: - What this branch changes and why: [ChangeSummary] - Framework, language, auth method, and data stores: [StackAndAuth] - Data that would hurt if exposed or changed: [SensitiveData] - Entry points and trust boundaries touched: routes, jobs, webhooks, file uploads: [EntryPoints] - Risks or past incidents you already know about: [KnownRisks] - Files or folders in scope, and anything out of scope: [ReviewScope] Generate: 1. Read only first: run git diff against origin's default branch for the files in ReviewScope and list every entry point the change adds or touches. Do not edit files in this pass. 2. Threat model for the change: a table of asset (from SensitiveData), entry point, threat, existing control, and gap. 3. Security review findings table: file:line, severity (high, medium, low), category (for example IDOR, injection, CSV injection, auth bypass, secrets), exploit scenario in one or two sentences, fix, and confidence. Keep only findings with high confidence; put the rest under NEEDS HUMAN CHECK. 4. Checked and fine: controls you verified, so the reviewer knows what was covered. 5. Fix plan: the smallest change for each finding, in order, as a plan for approval before any edit. 6. Tests to add: one failing test per finding that passes after the fix. Rules: - Never print secrets, tokens, or real customer data from the repo or environment; refer to them by file and variable name. - Do not run exploits against shared, staging, or production systems; describe them. - Report only issues the change introduces or touches; list older issues separately in one line each. - If a line number is unclear, quote the code instead of guessing.
Instructions
Open Claude Code at the root of your repo, check out the branch, fill in the brackets, and paste the prompt. Start in plan mode or ask it not to edit files until you approve the fix plan. Claude Code also ships a built-in /security-review command that runs an on-demand security pass over the changes on your current branch; this prompt adds a threat model and a test plan around that idea. Save it as a Markdown file in .claude/commands/ to reuse it as your own slash command. It does not replace a penetration test or your existing scanners. Use it only on code you own or are authorized to review.
Generated Output
This image was generated using the prompt above.

Examples
Example Input
ChangeSummary: adds GET /api/invoices/export so account admins can download their invoices as CSV, sorted by a column they pick.
StackAndAuth: Node.js with Express, PostgreSQL through the pg library, session cookie auth; req.session.user has id and accountId.
SensitiveData: invoice amounts, customer names and emails, other accounts' data.
EntryPoints: GET /api/invoices/export?accountId=&sort= in src/routes/invoices.js; CSV builder in src/lib/csv.js; query in src/db/invoices.js.
KnownRisks: last quarter a report endpoint returned another tenant's rows.
ReviewScope: src/routes/invoices.js, src/lib/csv.js, src/db/invoices.js. Code from the diff:
src/routes/invoices.js:42 const rows = await listInvoices(req.query.accountId, req.query.sort);
src/db/invoices.js:27 return db.query(`SELECT * FROM invoices WHERE account_id = $1 ORDER BY ${sort}`, [accountId]);
src/lib/csv.js:18 return rows.map(r => [r.customer_name, r.email, r.amount].join(',')).join('\n');Example Output
1. ENTRY POINTS
- New: GET /api/invoices/export (src/routes/invoices.js:42), reads accountId and sort from the query string.
- Touched: listInvoices in src/db/invoices.js:27; CSV builder in src/lib/csv.js:18.
2. THREAT MODEL
| Asset | Entry point | Threat | Existing control | Gap |
| Other accounts' invoices | accountId query param | Admin of account A requests account B | Session cookie auth | accountId is not checked against req.session.user.accountId |
| Database | sort query param | SQL injection through ORDER BY | accountId is a bound parameter | sort is placed into the SQL string |
| Customer names and emails in the CSV | CSV download opened in a spreadsheet | Formula injection from a customer name starting with = | None | Cells are not escaped |
3. FINDINGS
| File:line | Severity | Category | Exploit scenario | Fix | Confidence |
| src/routes/invoices.js:42 | High | IDOR | A logged-in admin changes accountId in the URL and downloads another account's invoices, the same pattern as last quarter's incident. | Ignore req.query.accountId and use req.session.user.accountId. | High |
| src/db/invoices.js:27 | High | SQL injection | sort is placed into ORDER BY, so a crafted sort value can change the query. | Map sort to an allowlist of column names (for example created_at, amount) and default to created_at. | High |
| src/lib/csv.js:18 | Medium | CSV injection | A customer named "=HYPERLINK(...)" becomes a live formula when an admin opens the file. Fields with commas also break columns. | Quote every field, double inner quotes, and prefix values starting with =, +, -, or @ with a single quote. | High |
NEEDS HUMAN CHECK
- Whether the export should require the admin role or any account member; the diff does not show a role check.
4. CHECKED AND FINE
- accountId is passed as a bound parameter ($1) in src/db/invoices.js:27.
- Session cookie auth is the stated auth method; confirm in the router file that /api/invoices/export is mounted behind it.
5. FIX PLAN (for approval, no edits yet)
1) src/routes/invoices.js:42: use req.session.user.accountId.
2) src/db/invoices.js:27: replace the ${sort} interpolation with an allowlisted column name.
3) src/lib/csv.js:18: add an escapeCell helper and use it for every field.
6. TESTS TO ADD
- Export with another account's accountId returns only the session account's invoices.
- sort=amount;DROP TABLE invoices returns 400 or falls back to created_at.
- A customer_name of =1+1 is written as '=1+1 in quotes in the CSV.