Loading...
Loading...
Security audit checklist based on OWASP Top 10 and best practices. Covers authentication, injection, XSS, CSRF, secrets management, and more. Use when reviewing security, before deploy, asking "is this secure", "security check", "vulnerability".
npx skill4agent add lee-to/ai-factory aif-security-checklist/aif-security-checklist/aif-security-checklist auth/aif-security-checklist injection/aif-security-checklist xss/aif-security-checklist csrf/aif-security-checklist secrets/aif-security-checklist api/aif-security-checklist infra/aif-security-checklist prompt-injection/aif-security-checklist race-condition/aif-security-checklist ignore <item>.ai-factory/SECURITY.md/aif-security-checklist ignore <item>.ai-factory/SECURITY.md/aif-security-checklist.ai-factory/SECURITY.md.ai-factory/SECURITY.md# Security: Ignored Items
Items below are excluded from security-checklist audits.
Review periodically — ignored risks may become relevant.
| Item | Reason | Date | Author |
|------|--------|------|--------|
| no-csrf | SPA with token auth, no cookies used | 2025-03-15 | @dev |
| no-rate-limit | Internal microservice, behind API gateway | 2025-03-15 | @dev |no-csrfno-rate-limitno-httpsno-xss-cspno-sql-injectionno-prompt-injectionno-race-conditionno-secret-rotationno-auth-{route}verbose-errors⏭️ Ignored Items (from .ai-factory/SECURITY.md)
┌─────────────────┬──────────────────────────────────────┬────────────┐
│ Item │ Reason │ Date │
├─────────────────┼──────────────────────────────────────┼────────────┤
│ no-csrf │ SPA with token auth, no cookies used │ 2025-03-15 │
│ no-rate-limit │ Internal service, behind API gateway │ 2025-03-15 │
└─────────────────┴──────────────────────────────────────┴────────────┘
⚠️ 2 items ignored. Run `/aif-security-checklist` without ignores to see full audit.bash ~/{{skills_dir}}/security-checklist/scripts/audit.sh✅ Requirements:
- [ ] Minimum 12 characters
- [ ] Hashed with bcrypt/argon2 (cost factor ≥ 12)
- [ ] Never stored in plain text
- [ ] Never logged
- [ ] Breach detection (HaveIBeenPwned API)references/AUTH-PATTERNS.md✅ Checklist:
- [ ] Session ID regenerated after login
- [ ] Session timeout implemented (idle + absolute)
- [ ] Secure cookie flags set
- [ ] Session invalidation on logout
- [ ] Concurrent session limits (optional)references/AUTH-PATTERNS.md✅ Checklist:
- [ ] Use RS256 or ES256 (not HS256 for distributed systems)
- [ ] Short expiration (15 min access, 7 day refresh)
- [ ] Validate all claims (iss, aud, exp, iat)
- [ ] Store refresh tokens securely (httpOnly cookie)
- [ ] Implement token revocation
- [ ] Never store sensitive data in payload// ❌ VULNERABLE: String concatenation
const query = `SELECT * FROM users WHERE id = ${userId}`;
// ✅ SAFE: Parameterized query
const user = await db.query('SELECT * FROM users WHERE id = $1', [userId]);
// ✅ SAFE: ORM (Prisma/Eloquent/SQLAlchemy)
const user = await prisma.user.findUnique({ where: { id: userId } });// ❌ VULNERABLE: Direct user input — attack: { "$ne": "" }
const user = await db.users.findOne({ username: req.body.username });
// ✅ SAFE: Type validation
const username = z.string().parse(req.body.username);// ❌ VULNERABLE: exec(`convert ${userFilename} output.png`);
// ✅ SAFE: execFile('convert', [userFilename, 'output.png']);- [ ] All user output HTML-encoded by default
- [ ] Content-Security-Policy header configured
- [ ] X-Content-Type-Options: nosniff
- [ ] Sanitize HTML if allowing rich text
- [ ] Validate URLs before rendering links// ❌ VULNERABLE: element.innerHTML = userInput; / dangerouslySetInnerHTML
// ✅ SAFE: element.textContent = userInput; / React: <div>{userInput}</div>
// ✅ If HTML needed: DOMPurify.sanitize(userInput)// ❌ VULNERABLE: <?= $userInput ?> / {!! $userInput !!}
// ✅ SAFE: {{ $userInput }} (Blade) / htmlspecialchars($input, ENT_QUOTES, 'UTF-8')default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; frame-ancestors 'none'; base-uri 'self'; form-action 'self'- [ ] CSRF tokens on all state-changing requests
- [ ] SameSite=Strict or Lax on cookies
- [ ] Verify Origin/Referer headers
- [ ] Don't use GET for state changescsurfsameSite: 'strict'❌ Secrets in code
const API_KEY = "sk_live_abc123";
❌ Secrets in git
.env committed to repository
❌ Secrets in logs
console.log(`Connecting with password: ${password}`);
❌ Secrets in error messages
throw new Error(`DB connection failed: ${connectionString}`);- [ ] Secrets in environment variables or vault
- [ ] .env in .gitignore
- [ ] Different secrets per environment
- [ ] Secrets rotated regularly
- [ ] Access to secrets audited
- [ ] No secrets in client-side code# If secrets were committed, remove from history
git filter-branch --force --index-filter \
"git rm --cached --ignore-unmatch path/to/secret-file" \
--prune-empty --tag-name-filter cat -- --all
# Or use BFG Repo-Cleaner (faster)
bfg --delete-files .env
bfg --replace-text passwords.txt
# Force push (coordinate with team!)
git push origin --force --all
# Rotate ALL exposed secrets immediately!- [ ] API keys not in URLs (use headers)
- [ ] Rate limiting per user/IP
- [ ] Request signing for sensitive operations
- [ ] OAuth 2.0 for third-party access// ✅ Validate all input with schema
import { z } from 'zod';
const CreateUserSchema = z.object({
email: z.string().email().max(255),
name: z.string().min(1).max(100),
age: z.number().int().min(0).max(150).optional(),
});
app.post('/users', (req, res) => {
const result = CreateUserSchema.safeParse(req.body);
if (!result.success) {
return res.status(400).json({ error: result.error });
}
// result.data is typed and validated
});// ✅ Don't expose internal errors
app.use((err, req, res, next) => {
console.error(err); // Log full error internally
// Return generic message to client
res.status(500).json({
error: 'Internal server error',
requestId: req.id, // For support reference
});
});
// ✅ Don't expose sensitive fields
const userResponse = {
id: user.id,
name: user.name,
email: user.email,
// ❌ Never: password, passwordHash, internalId, etc.
};app.use(helmet()); // Sets many security headers
// Or manually:
res.setHeader('X-Content-Type-Options', 'nosniff');
res.setHeader('X-Frame-Options', 'DENY');
res.setHeader('X-XSS-Protection', '0'); // Disabled, use CSP instead
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
res.setHeader('Permissions-Policy', 'camera=(), microphone=(), geolocation=()');# Check for vulnerabilities
npm audit
pip-audit
cargo audit
# Auto-fix where possible
npm audit fix
# Keep dependencies updated
npx npm-check-updates -u- [ ] HTTPS only (redirect HTTP)
- [ ] TLS 1.2+ only
- [ ] Security headers configured
- [ ] Debug mode disabled
- [ ] Default credentials changed
- [ ] Unnecessary ports closed
- [ ] File permissions restricted
- [ ] Logging enabled (but no secrets)
- [ ] Backups encrypted
- [ ] WAF/DDoS protection (for public APIs)references/RACE-CONDITIONS.md- [ ] Financial operations use database transactions with proper isolation
- [ ] Inventory/stock checks use atomic decrement (not read-then-write)
- [ ] Idempotency keys on payment and mutation endpoints
- [ ] Optimistic locking (version column) on concurrent updates
- [ ] File operations use exclusive locks where needed
- [ ] No TOCTOU gaps between permission check and action
- [ ] Rate limiting to reduce exploitation windowreferences/PROMPT-INJECTION.md- [ ] User input never concatenated directly into system prompts
- [ ] Input/output boundaries clearly separated (delimiters, roles)
- [ ] LLM output treated as untrusted (never executed as code/commands)
- [ ] Tool calls from LLM validated and sandboxed
- [ ] Sensitive data excluded from LLM context
- [ ] Rate limiting on LLM endpoints
- [ ] Output filtered for PII/secrets leakage
- [ ] Logging & monitoring for anomalous prompts# Find hardcoded secrets
grep -rn "password\|secret\|api_key\|token" --include="*.ts" --include="*.js" .
# Check for vulnerable dependencies
npm audit --audit-level=high
# Find TODO security items
grep -rn "TODO.*security\|FIXME.*security\|XXX.*security" .
# Check for console.log in production code
grep -rn "console\.log" src/
# Find prompt injection risks (unsanitized input in LLM calls)
grep -rn "system.*\${.*}" --include="*.ts" --include="*.js" .
grep -rn "innerHTML.*llm\|innerHTML.*response\|innerHTML.*completion" --include="*.ts" --include="*.js" .| Issue | Severity | Fix Timeline |
|---|---|---|
| SQL Injection | 🔴 Critical | Immediate |
| Auth Bypass | 🔴 Critical | Immediate |
| Secrets Exposed | 🔴 Critical | Immediate |
| XSS (Stored) | 🔴 Critical | < 24 hours |
| Prompt Injection (Direct) | 🔴 Critical | Immediate |
| Race Condition (Financial) | 🔴 Critical | Immediate |
| Prompt Injection (Indirect) | 🟠 High | < 1 week |
| Race Condition (Data) | 🟠 High | < 1 week |
| CSRF | 🟠 High | < 1 week |
| XSS (Reflected) | 🟠 High | < 1 week |
| Missing Rate Limit | 🟡 Medium | < 2 weeks |
| Verbose Errors | 🟡 Medium | < 2 weeks |
| Missing Headers | 🟢 Low | < 1 month |
Tip: Context is heavy after security audit. Consideror/clearbefore continuing with other tasks./compact