Skip to content

Add a security checklist to your repository

Secure coding guidelines requires every repository to keep its own stack-specific checklist. This page is how you create one, and — the part people skip — how you make the assistants actually read it.

Several repositories already have one: rekkari, inbox, my-vehicles, nettix-web, nettix-api, nettix-www, auth-api and maksuportaali. Copy the closest of those to your stack before starting from the template below.

.ai/security-checklist.md. That path is deliberately tool-neutral: no assistant owns it, so the same file serves Claude Code, Cursor and whatever the next tool is, and it sits next to the other .ai/ documents the repositories already keep.

What each file around it is for:

File Who reads it What belongs in it
.ai/security-checklist.md every assistant, and people the checklist itself — the canonical copy
AGENTS.md Cursor natively, and most other agents the hard rules, plus a pointer to .ai/
CLAUDE.md Claude Code only an @AGENTS.md import, plus Claude-specific notes
.claude/rules/security.md Claude Code, when it reads a matching file a short digest, scoped with paths:
.cursor/rules/security.mdc Cursor, when its globs match a short digest, referencing the canonical file

Keep the checklist in one place and let the rest point at it. Two copies of a security rule means one of them is wrong within a month.

Claude Code reads CLAUDE.md, not AGENTS.md, and a Markdown link is not an import. [AGENTS.md](AGENTS.md) in a sentence loads nothing; @AGENTS.md on its own line loads the file at session start.

@AGENTS.md
## Claude Code
- Before changing an API boundary, read `.ai/security-checklist.md`.

A path inside backticks stays literal, which is how you mention a file without pulling it into context.

For rules that should only load when the relevant code is open, add .claude/rules/security.md and scope it:

---
paths:
- "src/**/*.php"
- "web/src/**/*.{ts,tsx}"
---
Authorization is checked per resource, not per role. Input is validated before use.
Full checklist: `.ai/security-checklist.md`.

For Cursor, add .cursor/rules/security.mdc — a plain .md file in that directory is ignored, because the frontmatter is what Cursor keys on:

---
description: Security requirements for API boundaries and user input
globs: ["src/**/*.php", "web/src/**/*.{ts,tsx}"]
alwaysApply: false
---
Follow @.ai/security-checklist.md for anything touching an API boundary, user input,
payments or PII.

Cursor also reads AGENTS.md from the root and from subdirectories, so a repository with AGENTS.md plus the @AGENTS.md import in CLAUDE.md already covers both tools without a third copy of the rules.

Check that it worked: start a Claude Code session in the repository and run /context. The files listed under Memory files are the ones that actually loaded. If CLAUDE.md is missing there, nothing you wrote in it is reaching the agent.

Start from this and replace every bracketed part with what your stack actually does. The section names match Secure coding guidelines so a reviewer can move between the two.

# Security checklist — [repository]
Parent: https://docs.ajomark.dev/reference/secure-coding-guidelines/
That page states the requirements. This file says how they are met here, and where
this repository deviates.
## Quick reference (for PRs and plans)
- [ ] Authorization checked per resource, not only per role — [where that lives here]
- [ ] All request input validated before business logic — [validator or type layer]
- [ ] Queries parameterized — [ORM or query builder, and the forbidden pattern]
- [ ] Output escaped for its context — [template engine, autoescape status]
- [ ] Secrets from [AWS Secrets Manager / Parameter Store], never a committed file
- [ ] No PII in logs — [the fields that count as PII here]
- [ ] Uploads validated by content and size-limited — [handler]
- [ ] External lookups rate-limited and attributable — [which services, which limits]
- [ ] Security events logged — [logger, and how they are distinguishable]
- [ ] Errors return no internal detail — [error handler]
- [ ] Third-party actions pinned by SHA; internal reusable workflows at `@main`
[any workflow this repository must pin, and why]
- [ ] Workflow callers forward named secrets, not `secrets: inherit`
[the exceptions here, and why]
- [ ] No user-controlled `${{ github.event.* }}` in `run:` blocks — passed through `env:`
## Stack-specific patterns
### [Area, e.g. authorization]
Correct:
```[language]
[the pattern this repository uses]
```
Wrong:
```[language]
[the mistake that actually happens here]
```
## Deviations
| Requirement | Why not met | Compensating control | Approved by | Review date |
|---|---|---|---|---|
| [rule from the parent page] | [reason] | [what covers it meanwhile] | [owner] | [ISO date] |
A deviation covering a Critical or High finding also goes in the unit's continuity risk
assessment, with the engineering manager notified. This table alone does not reach the
unit's risk review.
## Enforcement
- Pre-commit: [what the hook blocks on]
- CI: [what `security-scan` runs, and whether it gates a merge]
- Action pinning: scored by Scorecard, gated by nothing — a review item
- Review: everything the three above cannot check

Keep it short enough that it gets read. Rules a tool already enforces belong in the tool, not in prose — the checklist guides an agent and a reviewer, and gates nothing on its own.