text
| 1 | # Secure Coding Rules |
| 2 | |
| 3 | A CLAUDE.md/AGENTS.md-style rules set. Load this alongside a project's own rules; it |
| 4 | constrains how code is written, not what feature to build. These rules apply to all |
| 5 | code the agent writes or edits, not only code explicitly flagged as "security-sensitive" |
| 6 | — the majority of real vulnerabilities show up in ordinary CRUD code, not crypto code. |
| 7 | |
| 8 | ## Secrets |
| 9 | |
| 10 | - Never hardcode API keys, passwords, tokens, connection strings, or private keys in |
| 11 | source, tests, fixtures, or comments — including "just for now" or "I'll remove it |
| 12 | later." Use environment variables or a secrets manager. |
| 13 | - Never print, log, or echo a secret's value, even for debugging. Log that a secret was |
| 14 | loaded/missing, not its contents. |
| 15 | - Never commit `.env` files with real values. Check `.gitignore` covers them before |
| 16 | creating one; if it doesn't, add it before writing the file. |
| 17 | - When a secret is genuinely needed in a prompt/example, use an obvious placeholder |
| 18 | (`sk-...REDACTED...`, `<YOUR_API_KEY>`), never a real-looking fake that could be |
| 19 | mistaken for a leaked credential. |
| 20 | - Rotate-on-suspicion: if a secret is found committed to history, treat it as |
| 21 | compromised — flag it for rotation, don't just delete the line in a new commit |
| 22 | (history still has it). |
| 23 | |
| 24 | ## Input validation |
| 25 | |
| 26 | - Validate all external input at the boundary: HTTP request bodies/params/headers, |
| 27 | query strings, file uploads, CLI arguments, environment variables, webhook payloads, |
| 28 | and data read from a queue. "External" means anything not constructed by this |
| 29 | process in this request. |
| 30 | - Validate shape *and* semantics: a string field being present isn't enough — check |
| 31 | length bounds, allowed character sets/formats, and business-rule bounds (e.g. |
| 32 | quantity > 0). |
| 33 | - Prefer allow-lists over deny-lists (enumerate what's valid, don't try to enumerate |
| 34 | every invalid thing). |
| 35 | - Never trust a client-supplied ID as sufficient authorization to act on that resource |
| 36 | — validation confirms shape, not permission (see Authorization below). |
| 37 | - Reject invalid input with a clear error; don't silently coerce/truncate it into |
| 38 | something "close enough." |
| 39 | |
| 40 | ## Injection prevention |
| 41 | |
| 42 | - SQL/NoSQL: always use parameterized queries or an ORM's query builder. Never build a |
| 43 | query by concatenating/interpolating untrusted input into a query string. |
| 44 | - Shell commands: avoid building commands from untrusted input at all where possible; |
| 45 | when unavoidable, use an API that passes arguments as an array (not a shell string) |
| 46 | and never pass untrusted input through `sh -c`/`shell: true`. |
| 47 | - Deserialization: don't deserialize untrusted data with formats/libraries that can |
| 48 | execute code (e.g. Python `pickle`, unsafe YAML loaders) — use safe/restricted |
| 49 | loaders for untrusted input. |
| 50 | - Template rendering: use the templating engine's auto-escaping; never mark untrusted |
| 51 | input as "safe"/raw HTML without a specific, reviewed reason. |
| 52 | |
| 53 | ## Authorization & authentication |
| 54 | |
| 55 | - Every new or modified endpoint, mutation, or background job that acts on a specific |
| 56 | resource must check that the *current* actor is allowed to act on *that specific* |
| 57 | resource — not just that they're authenticated. Watch for IDOR: a client-supplied ID |
| 58 | is not proof of ownership. |
| 59 | - Enforce authorization on the server/backend, never rely on a client (UI hiding a |
| 60 | button, a mobile app not showing a menu item) as the actual control. |
| 61 | - Default to deny: new routes/fields start unauthorized-by-default and are opened up |
| 62 | explicitly, not the reverse. |
| 63 | - Session tokens: use secure, httpOnly, sameSite cookies for web sessions where |
| 64 | applicable; never store auth tokens in localStorage if httpOnly cookies are an |
| 65 | option, since localStorage is readable by any script (XSS blast radius). |
| 66 | - Passwords: hash with a modern algorithm (bcrypt/scrypt/argon2) with a proper work |
| 67 | factor. Never MD5/SHA1/SHA256 alone (no salt/stretch) for password storage. |
| 68 | |
| 69 | ## Dependency hygiene |
| 70 | |
| 71 | - Before adding a new dependency: check it's actively maintained (recent releases), |
| 72 | reasonably popular/widely used, and has no known critical CVEs for the version being |
| 73 | added. |
| 74 | - Pin versions (exact or narrow range) in the lockfile; don't introduce a dependency |
| 75 | with an unbounded version range. |
| 76 | - Prefer the standard library or an existing dependency already in the project over |
| 77 | adding a new one for trivial functionality. |
| 78 | - When updating a dependency across a major version, actually read the changelog for |
| 79 | breaking/security-relevant changes rather than bumping blindly. |
| 80 | |
| 81 | ## Logging & PII |
| 82 | |
| 83 | - Never log full credentials, tokens, session IDs, or secrets — not even at debug |
| 84 | level. |
| 85 | - Treat as PII and avoid logging in plaintext unless there's a specific, reviewed need: |
| 86 | full names, email addresses, phone numbers, physical addresses, government IDs, |
| 87 | precise geolocation, health/financial data. Mask or omit by default (e.g. log a user |
| 88 | ID, not their email). |
| 89 | - Structured logs should have a defined field allow-list for what's safe to include; |
| 90 | don't `console.log`/`print` an entire request/response object that may contain |
| 91 | sensitive fields. |
| 92 | - Error messages returned to end users should not leak internal details (stack traces, |
| 93 | file paths, SQL, internal hostnames) — log the detail internally, return a generic |
| 94 | message externally. |
| 95 | |
| 96 | ## When in doubt |
| 97 | |
| 98 | If a change touches auth, payments, PII, or crypto and the right approach isn't |
| 99 | obvious from these rules, say so explicitly and ask rather than guessing — these are |
| 100 | exactly the categories where a quiet wrong guess becomes an incident. See |
| 101 | `checklists/owasp-quick.md` for a fast pre-ship pass over the OWASP Top 10 categories. |
| 102 |