Security and Hardening
Treat every external input as hostile, every secret as sacred, and every authorization check as mandatory. Security is a constraint on every line of code that touches user data, auth, or external systems — not a post-launch add-on.
Contract
Use this skill for features with external inputs, auth implementations, data storage changes, API integrations, and AI/LLM features.
Do not use for documentation-only or internal-only config changes with no external attack surface.
Stop and ask when a security decision requires human approval (see Ask First tier below).
Threat Model First
Before hardening any feature, spend five minutes thinking like an attacker:
- Map trust boundaries — where does untrusted data cross into the system? HTTP requests, file uploads, webhooks, third-party APIs, message queues, and LLM output.
- Name the assets — what's worth stealing or breaking? Credentials, PII, payment data, admin actions.
- Run STRIDE over each boundary:
| Threat |
Ask |
| Spoofing |
Can someone impersonate a user or service? |
| Tampering |
Can data be altered in transit or at rest? |
| Repudiation |
Can an action be denied later? |
| Information disclosure |
Can data leak? |
| Denial of service |
Can it be overwhelmed? |
| Elevation of privilege |
Can a user gain unauthorized rights? |
If you can't name the trust boundaries for a feature, you're not ready to secure it.
Three-Tier Boundary System
Always do (no exceptions):
- Validate all external input at the system boundary (API routes, form handlers)
- Parameterize all database queries — never concatenate user input into SQL
- Encode output to prevent XSS (use framework auto-escaping; don't bypass it)
- Use HTTPS for all external communication
- Hash passwords with bcrypt/scrypt/argon2 (never store plaintext; salt rounds ≥ 12)
- Set security headers (CSP, HSTS, X-Frame-Options, X-Content-Type-Options)
- Use httpOnly, secure, sameSite cookies for sessions
- Run dependency audit (
npm audit or equivalent) before every release
Ask first (requires human approval):
- Adding new authentication flows or changing auth logic
- Storing new categories of sensitive data (PII, payment info)
- Adding new external service integrations
- Changing CORS configuration
- Adding file upload handlers
- Modifying rate limiting or throttling
- Granting elevated permissions or roles
Never do:
- Never commit secrets to version control (API keys, passwords, tokens)
- Never log sensitive data (passwords, tokens, full card numbers)
- Never trust client-side validation as a security boundary
- Never use
eval() or innerHTML with user-provided data
- Never store session tokens in localStorage
- Never expose stack traces or internal error details to users
Key OWASP Prevention Patterns
See references/security-checklist.md for full code examples and the supply-chain hygiene checklist.
- Injection — parameterized queries or ORM; never concatenate input into SQL, shell commands, or template strings.
- Broken auth — bcrypt ≥ 12 salt rounds; httpOnly/secure/sameSite cookies; rate-limit login endpoints.
- XSS — framework auto-escaping by default; DOMPurify for any required HTML rendering; never
innerHTML with user data.
- Access control — check authorization (ownership/role), not just authentication, on every endpoint.
- SSRF — allowlist scheme + host + resolved IPs before any server-side URL fetch; reject redirects; validate after DNS resolution.
- Sensitive data — strip sensitive fields from API responses; environment variables for secrets; rotate any secret ever committed.
Securing AI / LLM Features
If the app calls an LLM, uses agents, or does RAG:
- Treat all model output as untrusted input — never pass LLM output into
eval, SQL, shell, innerHTML, or a file path. Validate and encode it exactly like raw user input.
- Assume prompts can be hijacked (Prompt Injection) — untrusted text in the context window (user messages, fetched pages, PDFs) can carry instructions. Enforce permissions in code, not the prompt.
- Keep secrets out of prompts — API keys, cross-tenant data, PII, and the full system prompt can be echoed back. Don't put them in the context window.
- Constrain tool/agent permissions — scope tools to the minimum needed, require confirmation for destructive or irreversible actions, validate every tool argument.
- Bound consumption — cap tokens, request rate, and loop/recursion depth to prevent cost runaway from crafted inputs.
- Isolate retrieval data (RAG) — partition embeddings per tenant so one user can't retrieve another's data; validate documents before indexing.
Red Flags
- User input passed directly to database queries, shell commands, or HTML rendering
- Secrets in source code or commit history
- API endpoints without authorization checks
- No rate limiting on authentication endpoints
- Stack traces or internal errors exposed to users
- Dependencies with known critical vulnerabilities
- Server fetches user-supplied URLs without an allowlist (SSRF)
- LLM/model output passed into a query, the DOM, a shell, or
eval
- Secrets, PII, or the full system prompt placed in an LLM context window
Verification
After implementing security-relevant code:
npm audit # no critical or high vulnerabilities
git diff --cached | grep -i "password\|secret\|api_key\|token" # no staged secrets
1---2name: security-and-hardening3description: Harden code against vulnerabilities. Use when handling user input, authentication, data storage, external integrations, or LLM/AI features. Use when building anything that accepts untrusted data, manages user sessions, or interacts with third-party services.4---56# Security and Hardening78Treat every external input as hostile, every secret as sacred, and every authorization check as mandatory. Security is a constraint on every line of code that touches user data, auth, or external systems — not a post-launch add-on.910## Contract1112Use this skill for features with external inputs, auth implementations, data storage changes, API integrations, and AI/LLM features.1314Do not use for documentation-only or internal-only config changes with no external attack surface.1516Stop and ask when a security decision requires human approval (see Ask First tier below).1718## Threat Model First1920Before hardening any feature, spend five minutes thinking like an attacker:21221. **Map trust boundaries** — where does untrusted data cross into the system? HTTP requests, file uploads, webhooks, third-party APIs, message queues, and **LLM output**.232. **Name the assets** — what's worth stealing or breaking? Credentials, PII, payment data, admin actions.243. **Run STRIDE over each boundary:**2526| Threat | Ask |27|--------|-----|28| **S**poofing | Can someone impersonate a user or service? |29| **T**ampering | Can data be altered in transit or at rest? |30| **R**epudiation | Can an action be denied later? |31| **I**nformation disclosure | Can data leak? |32| **D**enial of service | Can it be overwhelmed? |33| **E**levation of privilege | Can a user gain unauthorized rights? |3435If you can't name the trust boundaries for a feature, you're not ready to secure it.3637## Three-Tier Boundary System3839**Always do (no exceptions):**40- Validate all external input at the system boundary (API routes, form handlers)41- Parameterize all database queries — never concatenate user input into SQL42- Encode output to prevent XSS (use framework auto-escaping; don't bypass it)43- Use HTTPS for all external communication44- Hash passwords with bcrypt/scrypt/argon2 (never store plaintext; salt rounds ≥ 12)45- Set security headers (CSP, HSTS, X-Frame-Options, X-Content-Type-Options)46- Use httpOnly, secure, sameSite cookies for sessions47- Run dependency audit (`npm audit` or equivalent) before every release4849**Ask first (requires human approval):**50- Adding new authentication flows or changing auth logic51- Storing new categories of sensitive data (PII, payment info)52- Adding new external service integrations53- Changing CORS configuration54- Adding file upload handlers55- Modifying rate limiting or throttling56- Granting elevated permissions or roles5758**Never do:**59- Never commit secrets to version control (API keys, passwords, tokens)60- Never log sensitive data (passwords, tokens, full card numbers)61- Never trust client-side validation as a security boundary62- Never use `eval()` or `innerHTML` with user-provided data63- Never store session tokens in localStorage64- Never expose stack traces or internal error details to users6566## Key OWASP Prevention Patterns6768See `references/security-checklist.md` for full code examples and the supply-chain hygiene checklist.6970- **Injection** — parameterized queries or ORM; never concatenate input into SQL, shell commands, or template strings.71- **Broken auth** — bcrypt ≥ 12 salt rounds; httpOnly/secure/sameSite cookies; rate-limit login endpoints.72- **XSS** — framework auto-escaping by default; DOMPurify for any required HTML rendering; never `innerHTML` with user data.73- **Access control** — check authorization (ownership/role), not just authentication, on every endpoint.74- **SSRF** — allowlist scheme + host + resolved IPs before any server-side URL fetch; reject redirects; validate after DNS resolution.75- **Sensitive data** — strip sensitive fields from API responses; environment variables for secrets; rotate any secret ever committed.7677## Securing AI / LLM Features7879If the app calls an LLM, uses agents, or does RAG:8081- **Treat all model output as untrusted input** — never pass LLM output into `eval`, SQL, shell, `innerHTML`, or a file path. Validate and encode it exactly like raw user input.82- **Assume prompts can be hijacked (Prompt Injection)** — untrusted text in the context window (user messages, fetched pages, PDFs) can carry instructions. Enforce permissions in code, not the prompt.83- **Keep secrets out of prompts** — API keys, cross-tenant data, PII, and the full system prompt can be echoed back. Don't put them in the context window.84- **Constrain tool/agent permissions** — scope tools to the minimum needed, require confirmation for destructive or irreversible actions, validate every tool argument.85- **Bound consumption** — cap tokens, request rate, and loop/recursion depth to prevent cost runaway from crafted inputs.86- **Isolate retrieval data (RAG)** — partition embeddings per tenant so one user can't retrieve another's data; validate documents before indexing.8788## Red Flags8990- User input passed directly to database queries, shell commands, or HTML rendering91- Secrets in source code or commit history92- API endpoints without authorization checks93- No rate limiting on authentication endpoints94- Stack traces or internal errors exposed to users95- Dependencies with known critical vulnerabilities96- Server fetches user-supplied URLs without an allowlist (SSRF)97- LLM/model output passed into a query, the DOM, a shell, or `eval`98- Secrets, PII, or the full system prompt placed in an LLM context window99100## Verification101102After implementing security-relevant code:103104```bash105npm audit # no critical or high vulnerabilities106git diff --cached | grep -i "password\|secret\|api_key\|token" # no staged secrets107```108109- [ ] All user input validated at system boundaries110- [ ] Authentication and authorization checked on every protected endpoint111- [ ] Security headers present (verify with browser DevTools Network tab)112- [ ] Error responses don't expose internal details113- [ ] Rate limiting active on auth endpoints114- [ ] No secrets in source code or git history115- [ ] LLM output validated and encoded before use (if AI features present)116- [ ] Supply chain: lockfile committed; new dependencies reviewed for maintenance and postinstall scripts