Why do coding agents need security rules at all?
An AI coding agent optimizes for the feature you asked for. When you say "add a comments table", it creates the table and writes the query, and the page works in preview. Nothing in that loop rewards it for enabling Row Level Security, checking that the caller owns the row, or keeping the database secret out of the browser bundle. Those are things you have to ask for, every time, unless you write them down once.
Rules files are the place to write them down. Every major agent now reads a project-level instruction file at the start of a session, and most of them read a shared file called AGENTS.md as well. The idea is simple: instead of repeating "never put the secret key in client code" in every prompt, you say it once in a file the agent loads automatically.
The catch, which the rest of this post keeps coming back to, is that these files are advisory. Claude Code's own documentation says it treats CLAUDE.md as context, not enforced configuration, and that if you want to block an action regardless of what the model decides you should use a hook instead. Treat the rules as a way to cut the mistake rate, not to zero it.
The security rules file, ready to paste
Below is the file. It is written in plain English on purpose, because the agent reads it as instructions and because you should be able to understand every line you are asking it to follow. Copy it as-is, then trim anything that does not apply to your stack. Each rule is short and specific, which the Claude Code documentation says makes instructions more consistently followed than long or vague ones.
The file is deliberately under a hundred lines. Windsurf documents a 12,000 character limit per workspace rule file, and Claude Code recommends keeping CLAUDE.md under about 200 lines because longer files reduce adherence. A security file should be the part you never cut, so keep it small enough to survive.
# Security rules for this project
These rules apply to every change you make. If a request conflicts with a rule, stop and ask before proceeding.
## Secrets
- Never put secrets in client code. No API secret keys, service_role or sb_secret keys, database URLs, private keys or webhook secrets in anything that ships to a browser or mobile app.
- Only public keys (Supabase publishable or anon, Firebase web config) may appear in client code, and only because Row Level Security or Security Rules protect the data behind them.
- Read secrets from environment variables on the server. Never hardcode them, never log them, never commit .env files.
## Database
- Use parameterised queries or the ORM's query builder only. Never build SQL by concatenating or interpolating user input.
- Every new table must have Row Level Security enabled and at least one policy before it is used. Enabling RLS with no policies is the safe default; write the narrowest policy that makes the feature work.
- Never write a policy that is unconditionally true (for example: using (true)) on a table that holds user data.
- Never use the service_role or secret key to work around a permission error. Fix the policy instead.
- Firebase: never write allow read, write: if true; in Firestore, Realtime Database or Storage rules.
## Authorization
- Every API route, server action, edge function and RPC that touches a record must check that the signed-in user owns it or has a role that permits it. Do this on the server, never only in the UI.
- Never trust an id, role, price, or user_id sent by the client. Derive the user from the session and look up the rest.
## Input
- Validate every request body, query string and form input against a schema (zod, valibot, or the framework's validator) before using it.
- Reject unknown fields rather than passing them through to the database.
## Dangerous code
- Never use eval, new Function, or dynamic code execution on strings.
- Never disable TLS certificate verification (rejectUnauthorized: false, NODE_TLS_REJECT_UNAUTHORIZED=0, verify=False, InsecureSkipVerify).
- Never widen CORS to * on an authenticated API.
## Shell and SQL commands
- Ask before running any destructive shell or SQL command: rm -rf, git push --force, git reset --hard, DROP, TRUNCATE, DELETE without WHERE, migrations that drop columns, or anything against a production database.
- Never run commands against production credentials unless explicitly told that the task requires it.
## Dependencies
- Do not add a dependency without saying which package, which version, and why it is needed. Prefer the standard library or an existing dependency.
- Never install packages from a URL, a gist, or an unfamiliar registry.
## When unsure
- If you are not certain a change is safe, explain the risk and ask. Do not silently pick the insecure option to make an error disappear.Where does each tool read the file?
The rules are the same; the location differs by tool. The table maps each tool to the path it documents today and the scope at which the file applies. Where a tool also supports AGENTS.md, you can keep one file at the repo root and let the tool-specific file simply point to it.
| Tool | File path | Scope and notes |
|---|---|---|
| Cursor | .cursor/rules/*.mdc | Project rules, version-controlled, can be nested in subfolders. Four modes: Always Apply, Apply Intelligently, Apply to Specific Files (globs), Apply Manually via @-mention. Plain .md files in that folder are ignored. AGENTS.md at the root or in subdirectories is also read. User Rules are global and apply to Agent chat only. |
| Claude Code | ./CLAUDE.md or ./.claude/CLAUDE.md, plus .claude/rules/*.md | Loaded at the start of every session from the working directory and every directory above it. Rules in .claude/rules/ without a paths field load at launch; with paths they load only for matching files. ~/.claude/CLAUDE.md is personal and applies to all projects. CLAUDE.local.md is personal, add it to .gitignore. AGENTS.md is read on its own or alongside CLAUDE.md. |
| Windsurf | .windsurf/rules/*.md | Workspace rules, 12,000 characters per file. Activation via a trigger field: always_on, model_decision, glob, or manual. The docs (now hosted under docs.devin.ai) also list .devin/rules/*.md as preferred. Global rules live in ~/.codeium/windsurf/memories/global_rules.md, limited to 6,000 characters. Legacy .windsurfrules still works. |
| GitHub Copilot | .github/copilot-instructions.md | Repository-wide instructions, used by Copilot code review and the Copilot cloud agent. Path-specific files go in .github/instructions/NAME.instructions.md with an applyTo glob in frontmatter. AGENTS.md anywhere in the repo is also read, nearest file wins; CLAUDE.md or GEMINI.md at the root are accepted alternatives. |
| AGENTS.md convention | AGENTS.md at the repo root, optionally nested | Open format supported by more than twenty agents including Codex, Jules, Cursor, VS Code, Copilot, Aider and Zed. Plain Markdown, no required fields. In a monorepo the closest AGENTS.md to the file being edited takes precedence. |
How to install it in under five minutes
- 1Create AGENTS.md at the root of your repo and paste the rules file above into it. Cursor, Copilot, Claude Code and the other AGENTS.md-aware tools will pick it up from there.
- 2If you use Cursor, also create .cursor/rules/security.mdc. Put alwaysApply: true in the frontmatter so it loads in every chat rather than only when the agent decides it is relevant, then paste the same rules under it.
- 3If you use Claude Code, either keep the rules in AGENTS.md or add .claude/rules/security.md with no paths field so it loads at launch. Run /context in a session and check the Memory files list to confirm it loaded.
- 4If you use Windsurf, create .windsurf/rules/security.md with trigger: always_on in the frontmatter, and keep it under 12,000 characters.
- 5If you use GitHub Copilot review or the cloud agent, create .github/copilot-instructions.md containing the rules, or a one-line pointer to AGENTS.md.
- 6Commit all of these. They are part of the codebase and should travel with it to every collaborator and every clone.
Why rules reduce but do not eliminate insecure code
It is tempting to treat a rules file as a security control. It is not. The file is text that gets placed into the model's context at the start of a session. The model reads it, and usually follows it, but nothing stops it from ignoring a rule when the rule conflicts with the fastest way to make your request work. Claude Code's documentation states this plainly: CLAUDE.md instructions shape behaviour but are not a hard enforcement layer, and technical enforcement belongs in settings and hooks.
Rules also compete with everything else in context. A long conversation, a large file the agent just read, or a contradictory instruction in another rules file all dilute the security rules. The Claude Code docs note that if two rules contradict each other the model may pick one arbitrarily, and that adherence drops as instruction files grow. Cursor's Apply Intelligently mode goes further: the agent itself decides whether the rule is relevant, which for a security rule is exactly the decision you do not want it making.
Finally, rules only affect what the agent writes next. They do nothing for the tables it created last week, the policy it loosened to silence an error, or the key that has already shipped in a JavaScript bundle. Rules are a prevention habit. Something else has to look at what is actually deployed.
Pair the rules with a scan of what shipped
The rules file covers the writing side. The check on the shipped side is short and you can do it yourself. Open your deployed site, view the page source or the network tab, and search the JavaScript for strings that look like secret keys. Supabase's documentation says a secret key should never be in a browser, a shipped application, or source control, and that exposing one puts all of your project's data at risk. If you find one, rotate it, then find out how it got there.
Then test access as a stranger. Log out, and with only the public key try to read a table that should be private. Do the same as a second test user and try to read the first user's rows. If either works, RLS or rules are missing or unconditional, and the fix is in our Supabase RLS guide and Firebase rules guide, not in the agent's rules file.
If you would rather not do that by hand every release, a read-only external scan such as VibeSecurity can run the same checks against the public surface of the app on a schedule. Either way, the principle holds: the rules file lowers the rate at which mistakes are written, and the scan catches the ones that got through.
- Search the shipped bundle for secret keys, database URLs and private keys.
- Try reading private tables while logged out, using only the public key.
- Try reading another user's rows as a second test account.
- Check for policies that are literally true and for tables with RLS disabled.
- Repeat after every deploy, because the agent creates new tables between releases.
Keeping the rules file alive
A rules file decays the same way a README does. New tables get created, a teammate adds a second file that contradicts the first, and a rule written for Supabase confuses the agent when you move part of the app to Postgres on a server. Review the file when you change stack, and when a scan finds something the rules should have prevented, add a line for it.
Keep it short. Every line you add costs context on every session and slightly lowers the chance the important lines are followed. If a rule only matters for one folder, use the path-scoped mechanisms the tools offer: Cursor's file-pattern mode, Claude Code's paths frontmatter, Windsurf's glob trigger, Copilot's applyTo. Reserve the always-on file for the rules that must apply to every line of code, and that is exactly what a security file is.
Frequently asked questions
Where do Cursor rules for security go?
Cursor reads project rules from .cursor/rules/ as .mdc files, which are version-controlled and can be nested in subfolders. Set alwaysApply: true in the frontmatter so security rules load in every chat. Cursor also reads AGENTS.md from the project root or subdirectories, so one shared security file can serve Cursor and other tools at the same time.
Do AI coding agents always follow rules files?
No. Rules files are loaded as context, not enforced. Claude Code's documentation says CLAUDE.md instructions shape behaviour but are not a hard enforcement layer, and that blocking an action reliably requires a hook. Contradictory rules, long files and long conversations all lower adherence. Use rules to reduce mistakes, and use hooks and scans to catch what slips through.
What is AGENTS.md and should I use it instead of tool-specific files?
AGENTS.md is an open Markdown format for agent instructions, placed at the repository root with optional nested files where the nearest one wins. It is read by more than twenty agents, including Cursor, Copilot, Codex and Claude Code. Use it as the single source of truth, and keep tool-specific files only where you need a tool feature such as always-on activation.
Should the rules file mention my Supabase secret key?
Yes, explicitly. Tell the agent never to place the secret or service_role key in client code and never to use it to bypass a permission error. Supabase's documentation says secret keys bypass every Row Level Security policy and should never be in a browser, a shipped app or source control. The publishable key is the only one that belongs in client code.
How long should a security rules file be?
Short enough to be read every session. Windsurf caps workspace rule files at 12,000 characters, and Claude Code recommends keeping CLAUDE.md under roughly 200 lines because longer files reduce adherence. The file in this post is well under both limits. Put stack-specific detail in path-scoped rules and keep the always-on file to the rules that apply to every line.