| name | security-review |
| description | Security review checklist for dev-squad agents. Covers 10 security areas including secrets management, input validation, SQL injection, auth/authz, XSS, CSRF, rate limiting, sensitive data exposure, dependency security, and blockchain considerations. Includes pre-deployment checklist. |
Security Review - Security Audit for Dev Squad
INSTRUCTIONS: When this skill is invoked
The reviewer agent (security lead) MUST run through all 10 security areas when reviewing code. Other agents should reference this when building features that touch auth, user input, or data handling. Any P0-P1 finding blocks the merge.
Security Review Process
For each area below:
- Scan the codebase for relevant patterns
- Assess severity: P0 (critical), P1 (high), P2 (medium), P3 (low)
- Document findings with file path, line number, and remediation
- Block merge for any P0 or P1 finding
Severity Definitions
| Level | Definition | Response Time | Examples |
|---|
| P0 | Active exploitation possible, data breach imminent | Immediate fix, block all merges | Exposed secrets, SQL injection, auth bypass |
| P1 | High risk, exploitable with moderate effort | Fix before merge | Broken access control, XSS, missing rate limit on auth |
| P2 | Medium risk, defense-in-depth gap | Fix within sprint | Missing CSRF token, verbose error messages |
| P3 | Low risk, best practice gap | Track in backlog | Missing security headers, suboptimal password policy |
Area 1: Secrets Management
What to Check
- Hardcoded secrets, API keys, passwords, tokens in source code
- Secrets in configuration files committed to git
- Secrets in environment variable defaults
- Secrets in Docker images or build artifacts
- Secrets in log output
Detection Patterns
Search for these patterns in the codebase:
# High-signal patterns
password\s*=\s*["'][^"']+["']
api[_-]?key\s*=\s*["'][^"']+["']
secret\s*=\s*["'][^"']+["']
token\s*=\s*["'][^"']+["']
-----BEGIN (RSA |EC )?PRIVATE KEY-----
AWS_ACCESS_KEY_ID
STRIPE_SECRET_KEY
DATABASE_URL.*password
# Check .env files are gitignored
.env
.env.local
.env.production
Correct Pattern
const config = {
dbUrl: requireEnv("DATABASE_URL"),
jwtSecret: requireEnv("JWT_SECRET"),
stripeKey: requireEnv("STRIPE_SECRET_KEY"),
};
function requireEnv(name: string): string {
const value = process.env[name];
if (!value) {
throw new Error(`Missing required environment variable: ${name}`);
}
return value;
}
Remediation
- Move all secrets to environment variables or a secrets manager (Vault, AWS Secrets Manager)
- Add
.env* to .gitignore
- Rotate any secrets that were ever committed to git (even if removed later)
- Use
git-secrets or trufflehog in CI to prevent future leaks
- Redact secrets from log output
Area 2: Input Validation
What to Check
- All user inputs validated before processing (query params, body, headers, file uploads)
- Validation happens on the server side (client-side validation is UX, not security)
- Input length limits enforced
- File upload type and size restrictions
- JSON schema validation on API endpoints
Correct Pattern
import { z } from "zod";
const createUserSchema = z.object({
email: z.string().email().max(255),
name: z.string().min(1).max(100).trim(),
password: z.string().min(8).max(128),
role: z.enum(["user", "admin"]).optional().default("user"),
});
function validate(schema: z.ZodSchema) {
return (req: Request, _res: Response, next: NextFunction) => {
const result = schema.safeParse(req.body);
if (!result.success) {
throw new BadRequestError(
result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ")
);
}
req.body = result.data;
next();
};
}
const uploadSchema = z.object({
mimetype: z.enum(["image/jpeg", "image/png", "image/webp"]),
size: z.number().max(5 * 1024 * 1024),
});
Common Pitfalls
- Trusting
Content-Type header without validating actual file contents
- Not limiting array/object depth in JSON parsing
- Not sanitizing filenames in uploads (path traversal:
../../etc/passwd)
- Allowing unbounded string lengths (memory exhaustion)
Area 3: SQL Injection Prevention
What to Check
- All database queries use parameterized queries or prepared statements
- No string concatenation or template literals in SQL
- ORM usage does not bypass parameterization
- Raw queries (if any) use parameter binding
Detection Patterns
# Dangerous patterns to search for
query(`SELECT.*\$\{ # Template literal in SQL
query("SELECT.*" + # String concatenation in SQL
query(`.*${req. # User input directly in SQL
.where(`.*\$\{ # Template literal in WHERE clause
.raw(`.*\$\{ # Raw query with interpolation
Correct Pattern
const user = await db.query("SELECT * FROM users WHERE email = $1", [email]);
const user = await prisma.user.findUnique({ where: { email } });
const users = await knex("users").where("role", role).andWhere("active", true);
const user = await db.query(`SELECT * FROM users WHERE email = '${email}'`);
Go Example
row := db.QueryRow("SELECT id, name FROM users WHERE email = $1", email)
row := db.QueryRow(fmt.Sprintf("SELECT id, name FROM users WHERE email = '%s'", email))
Area 4: Authentication and Authorization
What to Check
- All non-public endpoints require authentication
- Authorization checks exist for every protected resource
- JWT tokens have reasonable expiry (15m access, 7d refresh)
- Password hashing uses bcrypt/argon2 with appropriate cost factor
- Account lockout after failed attempts
- Session invalidation on password change
- No broken object-level authorization (IDOR)
Correct Pattern
router.use("/api/v1", authenticate);
async function getOrder(req: Request, res: Response) {
const order = await orderRepo.findById(req.params.id);
if (!order) throw new NotFoundError("Order", req.params.id);
if (order.userId !== req.user.id && req.user.role !== "admin") {
throw new ForbiddenError("You do not have access to this order");
}
res.json(order);
}
const BCRYPT_ROUNDS = 12;
const hash = await bcrypt.hash(password, BCRYPT_ROUNDS);
const accessToken = jwt.sign(
{ sub: user.id, role: user.role },
jwtSecret,
{ expiresIn: "15m" }
);
IDOR (Insecure Direct Object Reference) Check
For every endpoint that accepts an ID parameter, verify:
- The authenticated user has permission to access that resource
- The check cannot be bypassed by changing the ID
- UUIDs are used (not sequential integers) to prevent enumeration
Area 5: XSS (Cross-Site Scripting) Prevention
What to Check
- All user-generated content is escaped/sanitized before rendering
- React/Vue/Angular auto-escaping is not bypassed
dangerouslySetInnerHTML (React) or v-html (Vue) usage is audited
- Content Security Policy (CSP) headers are set
- HTTP-only cookies for session tokens
Detection Patterns
# Dangerous patterns
dangerouslySetInnerHTML
v-html
innerHTML\s*=
document.write
eval(
new Function(
Correct Pattern
function Comment({ text }: { text: string }) {
return <p>{text}</p>;
}
import DOMPurify from "dompurify";
function RichContent({ html }: { html: string }) {
const sanitized = DOMPurify.sanitize(html, {
ALLOWED_TAGS: ["b", "i", "em", "strong", "a", "p", "ul", "ol", "li"],
ALLOWED_ATTR: ["href"],
});
return <div dangerouslySetInnerHTML={{ __html: sanitized }} />;
}
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:;
Area 6: CSRF (Cross-Site Request Forgery) Protection
What to Check
- State-changing operations (POST, PUT, PATCH, DELETE) have CSRF protection
- CSRF tokens are validated on the server
- SameSite cookie attribute is set
- CORS configuration is restrictive
Correct Pattern
app.use(session({
cookie: {
httpOnly: true,
secure: true,
sameSite: "strict",
maxAge: 24 * 60 * 60 * 1000,
},
}));
app.use(cors({
origin: ["https://myapp.com", "https://admin.myapp.com"],
credentials: true,
methods: ["GET", "POST", "PUT", "PATCH", "DELETE"],
}));
import { doubleCsrf } from "csrf-csrf";
const { doubleCsrfProtection } = doubleCsrf({
getSecret: () => process.env.CSRF_SECRET!,
cookieName: "__Host-csrf",
cookieOptions: { httpOnly: true, sameSite: "strict", secure: true },
});
app.use(doubleCsrfProtection);
Area 7: Rate Limiting
What to Check
- Authentication endpoints (login, signup, password reset) have strict rate limits
- API endpoints have general rate limits
- Rate limits are per-user/IP, not global
- Rate limit responses include retry-after information
Correct Pattern
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 5,
keyGenerator: (req) => req.ip,
handler: (req, res) => {
res.status(429).json({
error: "Too many attempts. Try again in 15 minutes.",
retryAfter: 900,
});
},
});
app.use("/api/v1/auth/login", authLimiter);
app.use("/api/v1/auth/signup", authLimiter);
app.use("/api/v1/auth/reset-password", authLimiter);
const apiLimiter = rateLimit({
windowMs: 60 * 1000,
max: 100,
keyGenerator: (req) => req.user?.id ?? req.ip,
});
app.use("/api/v1", apiLimiter);
Recommended Limits
| Endpoint | Window | Max Requests |
|---|
| Login | 15 min | 5 |
| Signup | 1 hour | 3 |
| Password reset | 1 hour | 3 |
| General API (authenticated) | 1 min | 100 |
| General API (unauthenticated) | 1 min | 20 |
| File upload | 1 hour | 10 |
Area 8: Sensitive Data Exposure
What to Check
- Passwords never returned in API responses
- PII (email, phone, address) exposure is minimized
- Error messages do not leak system internals (stack traces, SQL errors, file paths)
- Logs do not contain sensitive data
- HTTPS enforced everywhere (no mixed content)
- Sensitive headers stripped from responses
Correct Pattern
function toUserResponse(user: User): UserResponse {
return {
id: user.id,
email: user.email,
name: user.name,
role: user.role,
createdAt: user.createdAt,
};
}
function errorHandler(err: Error, req: Request, res: Response, next: NextFunction) {
if (err instanceof AppError) {
res.status(err.statusCode).json({ error: { code: err.code, message: err.message } });
} else {
logger.error({ err, requestId: req.requestId }, "unhandled error");
res.status(500).json({ error: { code: "INTERNAL_ERROR", message: "An unexpected error occurred" } });
}
}
const logger = pino({
redact: [
"req.headers.authorization",
"req.headers.cookie",
"password",
"passwordHash",
"token",
"creditCard",
"ssn",
],
});
app.use(helmet({
hsts: { maxAge: 31536000, includeSubDomains: true },
contentSecurityPolicy: true,
referrerPolicy: { policy: "strict-origin-when-cross-origin" },
}));
Area 9: Dependency Security
What to Check
- No known vulnerabilities in dependencies
- Dependencies are pinned to specific versions (lockfile exists)
- No unnecessary dependencies (attack surface)
- Automated vulnerability scanning in CI
Commands
npm audit
npm audit --production
govulncheck ./...
pip-audit
safety check
trivy fs .
snyk test
CI Integration
- name: Audit dependencies
run: npm audit --production --audit-level=high
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 10
Policy
- P0: Any dependency with a known critical/high CVE that affects the application
- P1: Dependencies with moderate CVEs in production code
- P2: Dependencies with low CVEs or dev-only vulnerabilities
- Block merge for P0-P1 until resolved (update, patch, or mitigate)
Area 10: Blockchain Security (If Applicable)
What to Check (Skip if not applicable)
- Smart contract reentrancy protection
- Integer overflow/underflow guards
- Access control on privileged functions
- Oracle manipulation risks
- Front-running vulnerabilities
- Private key management
- Transaction signing verification
Correct Pattern (Solidity)
// Reentrancy guard
contract Vault is ReentrancyGuard {
mapping(address => uint256) public balances;
function withdraw(uint256 amount) external nonReentrant {
require(balances[msg.sender] >= amount, "Insufficient balance");
// Update state BEFORE external call (checks-effects-interactions)
balances[msg.sender] -= amount;
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");
}
}
Correct Pattern (Key Management)
const signer = new ethers.Wallet(
await kmsClient.getPrivateKey("signing-key-id")
);
Pre-Deployment Security Checklist
Run through this checklist before any deployment. Every item must be checked.
Secrets and Configuration
Authentication and Authorization
Input and Output
Transport and Headers
Rate Limiting and Abuse Prevention
Dependencies and Infrastructure
Monitoring