Prompting For Secure Code
How to brief an AI so it gives you code you can actually trust — the same way you'd brief a junior developer who's technically brilliant but has never met a hacker.

In Part 1 we agreed that vibe coding is broadly a good thing, but that shipping AI-generated code without thinking about security is how you end up in a blog post written by someone at Wiz. Today we get practical. How do you actually write prompts that produce code you can trust?
Short answer: the same way you’d brief a junior developer who’s technically brilliant but has never met a hacker, has no idea what your business does, and believes every Stack Overflow answer is gospel. You have to tell it what you want, what you don’t want, and what “done” looks like.
Why this matters more than you’d think
There’s a lovely bit of research from Veracode in 2025 that found 45% of AI-generated code contains security flaws by default. That figure barely moves whether you’re using the latest model or one from last year. The AI isn’t getting dramatically worse at security — it’s just that security has never been its first priority. Making the code run is.
Here’s the key insight: you can cut that flaw rate dramatically just by asking for secure code. Sounds daft, but it works. The AI knows what secure code looks like. It’s seen millions of examples. It just needs you to tell it that’s what you want.
Which brings us to the golden rule of secure prompting:
The Golden Rule
The AI will give you what you ask for. So ask for the thing you actually want.
The five things every prompt should contain
Think of these as a checklist you run through before hitting send on anything that will handle real data.
01What the code does (obvious, but be specific)
Vague in, vague out. “Build me a login form” gets you the generic login form it has seen a thousand times — probably one that stores passwords in plain text because that’s what the tutorials in 2014 did.
Instead: “Build a login form that accepts email and password, validates email format, rate-limits to 5 attempts per minute per IP, and returns a generic error message for any failure (not ‘user not found’ vs ‘wrong password’).”
That last bit about the error message matters. Leaking whether an email exists is how attackers enumerate your user base. The AI knows this. But only if you remind it.
02The threat model (who are we worried about?)
This is the step almost nobody does, and it’s the single biggest lever you have.
“This endpoint will be public and unauthenticated” changes the code dramatically versus “this endpoint sits behind SSO and is only reachable inside our VPC.” The AI will happily write both, but it needs to know which.
Some useful framings to drop into prompts:
- Assume this will be called by untrusted clients over the public internet.
- Assume the database is shared with other services — strictly limit what this code can read or write.
- Assume at some point an attacker will have a valid user token and try to access another user’s data.
That last one is the magic phrase that makes AI remember to write authorisation checks, not just authentication checks. There is a difference, and it’s where an awful lot of breaches happen.
03The constraints (the “don’t do these things” list)
Be explicit about what’s off the table. A good starter:
- No hardcoded secrets, API keys, or credentials — use environment variables.
- No string concatenation in SQL queries — use parameterised queries.
- No eval, exec, or dynamic code execution on user input.
- No disabling TLS verification, even for testing.
- No logging of passwords, tokens, or full request bodies.
Pop these at the top of every prompt as a “house rules” block. The AI respects them far more than you’d expect.
04The environment (where will this run?)
“Write a database query” is one thing. “Write a Supabase query that runs client-side in a React component, assuming Row Level Security is enabled and the user’s JWT is available” is another thing entirely. The second version tells the AI to write code that relies on RLS rather than trying to enforce security in the browser (where it can’t be enforced, because the browser is the attacker’s computer).
If you’re using a specific stack — Supabase, Firebase, AWS Lambda, Next.js server actions, etc. — say so. The AI has patterns for each one, but it needs to know which set to reach for.
05What “done” looks like
“Write tests for the failure cases” is a beautiful closing line for a prompt. So is “list any assumptions you made about the environment” and “flag anything that should be reviewed by a security engineer.”
That last one is a bit of psychological judo — you’re inviting the AI to be honest about its own limits. It’s surprisingly willing to say “this assumes your framework handles CSRF tokens automatically; please verify that’s the case.”
A worked example
Let’s put it together. Here’s a bad prompt:
Bad prompt
Build a password reset feature.
Here’s a good one:
Good prompt
Build a password reset feature for a Node.js / Express API.
Context: This endpoint is public and unauthenticated. Users are identified by email. Tokens are stored in PostgreSQL.
Requirements:
- Generate a cryptographically secure random token (256 bits of entropy minimum).
- Hash the token before storing it in the database — never store the raw token.
- Token expires after 15 minutes and is single-use.
- Return the same generic response whether the email exists or not, to prevent user enumeration.
- Rate-limit to 3 requests per email per hour.
- Log attempts (without sensitive data) for monitoring.
Constraints:
- No hardcoded secrets. Pull SMTP credentials from environment variables.
- No string concatenation in SQL. Use parameterised queries.
- Do not log the token, the email body, or the request body.
When done: List the environment variables required, and flag any assumptions about rate-limiting infrastructure that need to be verified before deployment.
That prompt is four times longer, and you’ll get code that is about forty times more secure. Which is a fair trade.
Things to say when you want specific guardrails
A few phrases that punch well above their weight:
- Assume the input is hostile.Triggers input validation and sanitisation.
- This must work even if the attacker has a valid session.Triggers authorisation checks.
- Write this for defence in depth — don’t rely on the framework to handle X for you.Gets belt-and-braces code.
- Fail closed, not open.Tells the AI that if something’s ambiguous, deny the request rather than allow it.
- Treat every environment variable as potentially missing.Handles a whole class of deployment bugs.
- Include a paragraph in the comments explaining the security assumptions of this code.Forces the AI to be explicit, which also helps you spot when it’s wrong.
Things NOT to say
A few anti-patterns I’ve watched people fall into:
- “Make this as simple as possible” — often strips out security code. Simple is lovely; simple at the expense of validating input is not.
- “Just get it working” — a fine thing to say about your weekend project. A terrible thing to say about anything with users.
- “Don’t worry about edge cases” — edge cases are where security bugs live. Always worry about edge cases.
- “Do it the way [famous company] does it” — the AI doesn’t actually know how famous company does it. It will make something up that sounds plausible.
Review the code it gives back
Even with a perfect prompt, you’re still the reviewer. Three specific things to look for:
- Did it follow the rules you set? Scan for hardcoded strings that look like credentials. Search for
eval,exec,innerHTML,dangerouslySetInnerHTML,shell=True. Check the SQL. - Did it add any new dependencies? If you asked for JSON Web Tokens and it pulled in three npm packages, ask why. Every dependency is a small supply-chain risk.
- Can you explain what it’s doing? If you can’t, ask the AI to explain it. If the explanation still doesn’t make sense, you’re not ready to ship that bit. Park it and go and learn the thing.
The “explain it back to me” technique
Here’s one of my favourites. Once the AI has given you code, don’t just accept it. Paste it back and ask:
“Walk me through this code line by line, as if you’re reviewing it in a pull request. Explicitly identify any security-relevant decisions you made, any assumptions you’re relying on, and anything a senior engineer might push back on.”
You’ll be amazed at what comes out. Things like “I assumed CSRF protection is handled by the framework,” or “I used md5 for hashing because you didn’t specify — you should use bcrypt or argon2 for passwords.” Things that absolutely should have been in the first response, but weren’t.
This technique catches more bugs than any static analyser.
One last thing: save your prompts
When the AI writes a chunk of code for you, save the prompt that produced it. Not in a fancy way — a PROMPTS.md file in your repo is fine. Note what you asked for and any follow-up refinements.
Two reasons. First, when the code breaks in six months, the prompt is often the fastest way back to understanding what it was trying to do. Traditional code has comments and commit messages; vibe-coded code has prompts. Treat them with the same respect.
Second, if you find a prompt that produces consistently good secure code for a particular task, reuse it. Build yourself a library of tested prompts. It’s the vibe-coding equivalent of a secure-by-default template library.
The bottom line
Prompting well isn’t a secret art. It’s a brief. The clearer the brief, the better the code. And when that code is going to handle someone else’s data, the brief has to include security — explicitly, repeatedly, specifically.
The AI is a tool. A remarkably good tool, but still a tool. The responsibility for what it builds is yours. Write prompts like you mean that.
Next in the series — Part 3 of 5
Cloud security for the accidentally-successful SaaS founder — RLS, IAM, secrets management, in plain English. If you’ve ever wondered what the Moltbook team missed, this is the one.