security
Use when applying security best practices — authentication, authorization, CSRF protection, input sanitization, rate limiting, or secure coding — stack-agnostic.
SKILL.mdsrc/skills/security/SKILL.md
GitHubsecurity
When to use
Use when implementing authentication, authorization, or other security-sensitive work.
Do NOT use when:
- Validation logic only — route to the project's validation carve-out (
laravel-validationfor Laravel; otherwise the framework-native primitive — Zod / class-validator, Pydantic, struct-tag validators). - Full security audit — route to
security-audit. - You need a pre-implementation threat model — route to
threat-modeling. - You need end-to-end authorization analysis — route to
authz-review.
Stack-specific carve-outs
The procedure below is stack-agnostic. For framework-specific primitives (Laravel Policies / Gates / FormRequests, Symfony voters, NestJS guards, Next.js middleware), defer to:
| Stack | Carve-out |
|---|---|
| Laravel | laravel, laravel-validation, laravel-middleware |
| Symfony | symfony-workflow |
| Next.js / TS | nextjs-patterns |
Procedure: Implement security for a feature (stack-neutral)
Step 0: Inspect
- Read the project's auth doc (
agents/authentication.md,docs/auth.md, or framework docs). - Read the project's authorization doc (gates / policies / voters / guards).
- Locate existing authorization rules in the project's idiomatic location (Laravel
app/Policies/, Symfonysrc/Security/Voter/, NestJS*.guard.ts).
Step 1: Authentication
- Identify the auth mechanism in use (session, JWT, OAuth, API token) — read the framework's auth config (
config/auth.php,next-auth.config.ts, Symfonysecurity.yaml, FastAPI dependency). - Check guard / strategy / provider configuration.
- Multi-tenant identification happens after authentication — see
multi-tenancy.
Step 2: Authorization
- Create / locate the authz rule in the framework's idiomatic primitive (Policy, voter, guard, middleware, route dependency).
- Apply it at the request boundary (FormRequest
authorize(), controller / route-handler dependency, middleware chain). - Cover non-model gates (cross-aggregate rules) — keep them centralised, not scattered across handlers.
Step 3: Review for adversarial
For security-sensitive changes, run adversarial-review.
Focus: attack surface, trusted user input, authz gaps.
Conventions
→ For PHP / Laravel specifics (auth helpers, mass assignment, Blade escaping, CSRF middleware): see guideline docs/guidelines/php/security.md.
→ For other stacks, follow the framework's hardening guide and the carve-outs above.
Account flows — the whole lifecycle, not just the login
Sign-up, verification, reset, credential and email change, MFA, federated sign-in and logout are one surface. Before judging any of them, trace the path: input → identity lookup → verification → secret handling → state change → session → response.
- A public auth response is an observable contract, not an error string: status, body category, redirect and cookies must not differ between a known and an unknown identity, and the verifier path runs for both.
- Compare every secret with the library's constant-time primitive, and respect that primitive's length precondition. Never a general-purpose fast hash for a password. Never an invented token, one-time code or salt scheme.
- Changing an identity — address, phone, password, factor, passkey, recovery codes, a linked provider — needs fresh re-authentication and a notice on the channel that was already verified.
Ground the specifics before enumerating from memory: the account-lifecycle
failure classes, their required controls and their negative tests live in
threat-modeling's corpus under the
authentication surface class. Every numeric value in them defers to the
external authority named in the block below.
Crypto, password storage, certificates — route, do not guess
This skill carries no cryptographic parameter, key size, work factor, cipher suite, or TLS version floor: such a value is authoritative-looking long after it stops being true. Take it from https://cheatsheetseries.owasp.org/ when you need it — never from memory, never from this file. Rationale and reopening condition: ADR-238.
Validate
- Verify all user input is validated at the boundary via the framework's primitive — never trust raw request data.
- Confirm an authorization check exists for every state-changing action.
- Check that no raw user input reaches SQL, HTML output, shell commands, or template renderers without escaping.
- Run the project's type-checker — must pass (type errors can enable injection).
Output format
- Security-hardened code with auth, input validation at the boundary, and output encoding.
- Authorization rule (Policy / voter / guard / middleware) co-located with the route.
Gotcha
- Validation ensures format, not intent — don't trust input after validation alone.
- "Throw" vs "boolean" authz APIs behave differently (
Gate::authorize()throws vsGate::allows()returns bool in Laravel;CanActivatein NestJS throws; FastAPI dependencies throwHTTPException). Pick per how the framework surfaces failure. - Rate-limit ALL public endpoints, not just login.
- Never log passwords, tokens, or API keys.
Do NOT
- Do NOT bypass the framework's request-validation primitive inside handlers.
- Do NOT bulk-bind raw request payloads to ORM entities without an explicit allow-list (
$fillable/$guarded, DTO mapping, Pydantic model). - Do NOT store plaintext passwords or secrets in the database.
- Do NOT expose internal error details in production API responses.
Auto-trigger keywords
- security
- authentication
- authorization
- CSRF
- XSS
- policy