Write a security rules file once, and every prompt inherits it
Every AI coding tool reads a standing instructions file before it answers, and most people leave theirs empty of security constraints. Four rules in that file change the default shape of everything the tool generates afterwards, which is cheaper than reviewing each output and far more reliable.
If your prompt never mentions security, your generated code will not feature any. That sounds glib, and it is the single most useful sentence in this article.
A language model writes what satisfies the request in front of it. "Let users upload a profile picture" is a complete instruction, and a complete answer to it is an upload endpoint that works. Whether that endpoint accepts a 400MB .php file was never part of the question, so it was never part of the answer.
You can fix that one prompt at a time, adding "and make it secure" to every request and hoping the model picks the same interpretation twice. Or you can write the constraints down once, in a file the tool reads before it answers anything.
What is a rules file?
Every serious AI coding tool now reads standing instructions from a file in your repository. It loads them into context before it looks at your prompt, so they are present for every request rather than the one you remember to add them to.
| Tool | File |
|---|---|
| Claude Code | CLAUDE.md |
| Cursor | .cursorrules, or .cursor/rules/*.mdc |
| Windsurf | .windsurfrules |
| Codex and several others | AGENTS.md |
| GitHub Copilot | .github/copilot-instructions.md |
The mechanics differ slightly between them and the effect is the same. Anything in that file is present for every request, so a constraint written once applies to code you have not thought of yet.
Most repositories that have one use it for style and architecture, telling the model which framework to use and where files live. Very few carry security constraints, which is the gap this article is about.
What actually belongs in it?
Four rules. Not forty, and the reason for that matters.
Rules files compete with your actual prompt for the model's attention, and a long one dilutes itself. Practitioners who have tuned these settle around 200 lines as the point where compliance starts dropping, and security constraints buried at line 340 of a style guide are constraints in name only. Pick the four that change outcomes.
1. Secrets never reach the client.
Never place an API key, token, database URL or secret in client-side code, in a
NEXT_PUBLIC_orVITE_prefixed variable, or in any file that is bundled for the browser. Secrets live in server-side environment variables and are used only from server routes. If a feature seems to need a secret in the browser, stop and tell me rather than working around it.
That last sentence is the important half. Without it the model resolves the conflict silently, and it resolves it in favour of the feature working.
2. Authorisation is checked on the server, every time.
Every API route must verify the session and confirm the signed-in user owns the resource before returning or modifying anything. Client-side redirects are for user experience only and are never the security boundary. When adding a route that reads or writes user data, add the ownership check in the same change.
This is the rule that prevents the most common serious flaw in generated apps, where user A reads user B's records by changing a number in a URL. It is worth its place in the file on that basis alone.
3. Database access is closed by default.
When creating a Supabase or Postgres table, enable Row Level Security, revoke the default grants from
anonandauthenticated, then grant back only the operations the app uses, and write one policy per operation. Never useusing (true). Views must be created withsecurity_invoker = true.
The revoke step is in there because enabling RLS does not remove the privileges granted when the table was created. That is Supabase's own documented behaviour, and it is the step almost every guide omits.
4. Dependencies are verified before they are installed.
Before adding any dependency, confirm the package exists on the registry, has more than one published version, links to a public repository, and has no install lifecycle scripts. If any check fails, stop and tell me rather than installing it.
That one exists because models invent package names, and attackers register the invented ones. A hallucinated npm package called react-codeshift reached 237 repositories that way, propagating through shared agent instruction files rather than through anything a human typed. The longer version of that story is here.
Does a rules file actually work?
Partly, and being honest about the limits is what makes the rest of this usable. A rules file is not a control, whatever it feels like when you write one.
A rules file shifts the default. Asked to add a payment webhook with rule two in context, the model is substantially more likely to write the signature check unprompted, because the constraint is sitting in front of it while it works. That is real and it is worth the twenty minutes.
What it is not is an enforcement mechanism. The file is text in a context window competing with everything else in that window, and compliance degrades as a session gets long, as the file gets long, and as your actual request pulls harder in another direction. A rule that says "never put secrets in the client" and a prompt that says "just make the chatbot work" will sometimes resolve the wrong way.
So treat the file as changing the odds rather than closing the hole. The things that actually close holes are the same as they were: checks that run against the code after it exists, and a person or a scanner reading what shipped.
Where do I put it, and what about existing code?
Put it at the repository root, commit it, and keep it under version control like anything else that governs how the project is built. An uncommitted rules file protects only the machine it sits on.
Two practical notes that catch people. If you use more than one tool, you need the file each tool reads, because Cursor does not read CLAUDE.md. Several teams keep one canonical file and symlink or copy it to the other names rather than maintaining four versions that drift apart.
And a rules file changes what gets written from now on. It does nothing about the code already in your repository, which was generated before the file existed and under exactly the conditions the file is meant to prevent. If you are adding one today, the honest sequence is to write the file, then audit what is already there.
For the audit, six checks you can run in fifteen minutes covers the flaws that actually get people. If you would rather have something read every file than work through a list by hand, that is what Sentrint does: your whole repository, in plain English, with a fix written for the tool that built the app. A code scan, not a pentest, and the first one is free with no card.
Write the file first though. It takes twenty minutes and it changes the shape of everything you build after today.