.agents/skills/owasp-security/SKILL.md
Implement secure coding practices following OWASP Top 10. Use when preventing security vulnerabilities, implementing authentication, securing APIs, or conducting security reviews. Triggers on OWASP, security, XSS, SQL injection, CSRF, authentication security, secure coding, vulnerability.
npx skillsauth add toricodesthings/Cadence owasp-securityInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Prevent common security vulnerabilities in web applications.
| # | Vulnerability | Prevention | |---|---------------|------------| | A01 | Broken Access Control | Proper authorization checks | | A02 | Cryptographic Failures | Strong encryption, secure storage | | A03 | Injection | Input validation, parameterized queries | | A04 | Insecure Design | Threat modeling, secure patterns | | A05 | Security Misconfiguration | Hardened configs, no defaults | | A06 | Vulnerable Components | Dependency scanning, updates | | A07 | Auth Failures | MFA, secure session management | | A08 | Data Integrity Failures | Input validation, signed updates | | A09 | Logging Failures | Comprehensive audit logs | | A10 | SSRF | URL validation, allowlists |
// ❌ BAD: No authorization check
app.get('/api/users/:id', async (req, res) => {
const user = await db.users.findById(req.params.id);
res.json(user);
});
// ✅ GOOD: Verify ownership
app.get('/api/users/:id', authenticate, async (req, res) => {
const userId = req.params.id;
// Users can only access their own data
if (req.user.id !== userId && req.user.role !== 'admin') {
return res.status(403).json({ error: 'Forbidden' });
}
const user = await db.users.findById(userId);
res.json(user);
});
// ✅ GOOD: Role-based access control (RBAC)
const requireRole = (...roles: string[]) => {
return (req: Request, res: Response, next: NextFunction) => {
if (!roles.includes(req.user?.role)) {
return res.status(403).json({ error: 'Insufficient permissions' });
}
next();
};
};
app.delete('/api/posts/:id', authenticate, requireRole('admin', 'moderator'), deletePost);
// ❌ BAD: Predictable IDs exposed
GET /api/invoices/1001
GET /api/invoices/1002 // Can enumerate others' invoices
// ✅ GOOD: Use UUIDs + ownership check
app.get('/api/invoices/:id', authenticate, async (req, res) => {
const invoice = await db.invoices.findOne({
id: req.params.id,
userId: req.user.id, // Enforce ownership
});
if (!invoice) {
return res.status(404).json({ error: 'Not found' });
}
res.json(invoice);
});
import bcrypt from 'bcrypt';
import crypto from 'crypto';
// ✅ Hash passwords with bcrypt
const SALT_ROUNDS = 12;
async function hashPassword(password: string): Promise<string> {
return bcrypt.hash(password, SALT_ROUNDS);
}
async function verifyPassword(password: string, hash: string): Promise<boolean> {
return bcrypt.compare(password, hash);
}
// ✅ Secure token generation
function generateSecureToken(length = 32): string {
return crypto.randomBytes(length).toString('hex');
}
// ✅ Encrypt sensitive data
const ALGORITHM = 'aes-256-gcm';
const KEY = crypto.scryptSync(process.env.ENCRYPTION_KEY!, 'salt', 32);
function encrypt(text: string): { encrypted: string; iv: string; tag: string } {
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv(ALGORITHM, KEY, iv);
let encrypted = cipher.update(text, 'utf8', 'hex');
encrypted += cipher.final('hex');
return {
encrypted,
iv: iv.toString('hex'),
tag: cipher.getAuthTag().toString('hex'),
};
}
function decrypt(encrypted: string, iv: string, tag: string): string {
const decipher = crypto.createDecipheriv(ALGORITHM, KEY, Buffer.from(iv, 'hex'));
decipher.setAuthTag(Buffer.from(tag, 'hex'));
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
import helmet from 'helmet';
app.use(helmet());
app.use(helmet.hsts({ maxAge: 31536000, includeSubDomains: true }));
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'strict-dynamic'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", 'data:', 'https:'],
connectSrc: ["'self'"],
fontSrc: ["'self'"],
objectSrc: ["'none'"],
frameAncestors: ["'none'"],
},
}));
// ❌ BAD: String concatenation
const query = `SELECT * FROM users WHERE email = '${email}'`;
// ✅ GOOD: Parameterized queries
// With Prisma
const user = await prisma.user.findUnique({ where: { email } });
// With raw SQL (parameterized)
const user = await db.query('SELECT * FROM users WHERE email = $1', [email]);
// With Knex
const user = await knex('users').where({ email }).first();
// ❌ BAD: Direct user input in query
const user = await User.findOne({ username: req.body.username });
// Attack: { "username": { "$gt": "" } } returns first user
// ✅ GOOD: Validate input type
import { z } from 'zod';
const loginSchema = z.object({
username: z.string().min(3).max(50),
password: z.string().min(8),
});
app.post('/login', async (req, res) => {
const { username, password } = loginSchema.parse(req.body);
const user = await User.findOne({ username: String(username) });
// ...
});
import { execFile } from 'child_process';
// ❌ BAD: Shell injection
exec(`convert ${userInput} output.png`); // userInput: "; rm -rf /"
// ✅ GOOD: Use execFile with array args
execFile('convert', [userInput, 'output.png'], (error, stdout) => {
// Safe - arguments are not shell-interpreted
});
// ✅ GOOD: Validate and sanitize
const allowedFormats = ['png', 'jpg', 'gif'];
if (!allowedFormats.includes(format)) {
throw new Error('Invalid format');
}
import rateLimit from 'express-rate-limit';
// General rate limit
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // 100 requests per window
standardHeaders: true,
legacyHeaders: false,
});
// Strict limit for auth endpoints
const authLimiter = rateLimit({
windowMs: 60 * 60 * 1000, // 1 hour
max: 5, // 5 failed attempts
skipSuccessfulRequests: true,
});
app.use('/api/', limiter);
app.use('/api/auth/', authLimiter);
import { z } from 'zod';
const userSchema = z.object({
email: z.string().email(),
password: z.string()
.min(8)
.regex(/[A-Z]/, 'Must contain uppercase')
.regex(/[a-z]/, 'Must contain lowercase')
.regex(/[0-9]/, 'Must contain number')
.regex(/[^A-Za-z0-9]/, 'Must contain special character'),
age: z.number().int().min(13).max(120),
role: z.enum(['user', 'admin']).default('user'),
});
app.post('/api/users', async (req, res) => {
try {
const data = userSchema.parse(req.body);
// Validated data is safe to use
} catch (error) {
if (error instanceof z.ZodError) {
return res.status(400).json({ errors: error.errors });
}
throw error;
}
});
// ✅ Never expose stack traces in production
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
console.error(err.stack); // Log for debugging
res.status(500).json({
error: process.env.NODE_ENV === 'production'
? 'Internal server error'
: err.message,
});
});
// ✅ Disable sensitive headers
app.disable('x-powered-by');
// ✅ Secure cookie configuration
app.use(session({
secret: process.env.SESSION_SECRET!,
cookie: {
secure: process.env.NODE_ENV === 'production',
httpOnly: true,
sameSite: 'strict',
maxAge: 24 * 60 * 60 * 1000, // 24 hours
},
resave: false,
saveUninitialized: false,
}));
# Check for vulnerabilities
npm audit
npm audit fix
# Use Snyk for deeper scanning
npx snyk test
npx snyk monitor
# Keep dependencies updated
npx npm-check-updates -u
// package.json - Use exact versions or ranges
{
"dependencies": {
"express": "^4.18.0", // Minor updates OK
"lodash": "4.17.21" // Exact version
},
"overrides": {
"vulnerable-package": "^2.0.0" // Force safe version
}
}
import jwt from 'jsonwebtoken';
// ✅ JWT with short expiry + refresh tokens
function generateTokens(userId: string) {
const accessToken = jwt.sign(
{ userId },
process.env.JWT_SECRET!,
{ expiresIn: '15m' } // Short-lived
);
const refreshToken = jwt.sign(
{ userId, type: 'refresh' },
process.env.JWT_REFRESH_SECRET!,
{ expiresIn: '7d' }
);
return { accessToken, refreshToken };
}
// ✅ Secure password reset
async function initiatePasswordReset(email: string) {
const user = await db.users.findByEmail(email);
if (!user) return; // Don't reveal if email exists
const token = crypto.randomBytes(32).toString('hex');
const hashedToken = crypto.createHash('sha256').update(token).digest('hex');
await db.passwordResets.create({
userId: user.id,
token: hashedToken,
expiresAt: new Date(Date.now() + 60 * 60 * 1000), // 1 hour
});
await sendEmail(email, `Reset link: /reset?token=${token}`);
}
import { authenticator } from 'otplib';
import QRCode from 'qrcode';
// Setup TOTP
async function setupMFA(userId: string) {
const secret = authenticator.generateSecret();
const otpauth = authenticator.keyuri(userId, 'MyApp', secret);
const qrCode = await QRCode.toDataURL(otpauth);
await db.users.update(userId, { mfaSecret: encrypt(secret) });
return { qrCode, secret };
}
// Verify TOTP
function verifyMFA(token: string, secret: string): boolean {
return authenticator.verify({ token, secret });
}
// ✅ React auto-escapes by default
const UserProfile = ({ user }) => (
<div>{user.name}</div> // Safe - auto-escaped
);
// ⚠️ Dangerous - avoid if possible
<div dangerouslySetInnerHTML={{ __html: sanitizedHtml }} />
// ✅ Sanitize HTML if needed
import DOMPurify from 'dompurify';
const sanitizedHtml = DOMPurify.sanitize(userHtml, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a'],
ALLOWED_ATTR: ['href'],
});
// ✅ Content Security Policy
app.use(helmet.contentSecurityPolicy({
directives: {
scriptSrc: ["'self'"], // No inline scripts
styleSrc: ["'self'", "'unsafe-inline'"],
},
}));
import winston from 'winston';
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' }),
],
});
// ✅ Log security events
function logSecurityEvent(event: string, details: object) {
logger.warn({
type: 'security',
event,
...details,
timestamp: new Date().toISOString(),
});
}
// Usage
logSecurityEvent('failed_login', { email, ip: req.ip, userAgent: req.headers['user-agent'] });
logSecurityEvent('access_denied', { userId, resource, action });
logSecurityEvent('suspicious_activity', { userId, pattern: 'rapid_requests' });
import { URL } from 'url';
// ✅ Validate URLs against allowlist
const ALLOWED_HOSTS = ['api.example.com', 'cdn.example.com'];
function isAllowedUrl(urlString: string): boolean {
try {
const url = new URL(urlString);
// Block private IPs
const privatePatterns = [
/^localhost$/i,
/^127\./,
/^10\./,
/^172\.(1[6-9]|2[0-9]|3[01])\./,
/^192\.168\./,
/^0\./,
/^169\.254\./, // Link-local
];
if (privatePatterns.some(p => p.test(url.hostname))) {
return false;
}
// Check allowlist
return ALLOWED_HOSTS.includes(url.hostname);
} catch {
return false;
}
}
app.post('/api/fetch-url', async (req, res) => {
const { url } = req.body;
if (!isAllowedUrl(url)) {
return res.status(400).json({ error: 'URL not allowed' });
}
const response = await fetch(url);
// ...
});
## Pre-Deployment Checklist
### Authentication
- [ ] Passwords hashed with bcrypt (cost ≥ 12)
- [ ] JWT tokens have short expiry
- [ ] Session cookies are httpOnly, secure, sameSite
- [ ] Rate limiting on auth endpoints
### Authorization
- [ ] All endpoints have auth checks
- [ ] RBAC implemented correctly
- [ ] No IDOR vulnerabilities
### Input/Output
- [ ] All input validated with Zod/Joi
- [ ] SQL queries parameterized
- [ ] XSS prevented (CSP, escaping)
- [ ] File uploads validated and sandboxed
### Infrastructure
- [ ] HTTPS enforced
- [ ] Security headers configured
- [ ] Dependencies audited
- [ ] Secrets in environment variables
### Monitoring
- [ ] Security events logged
- [ ] Error monitoring enabled
- [ ] Alerts configured
development
Reviews and authors Cloudflare Workers code against production best practices. Load when writing new Workers, reviewing Worker code, configuring wrangler.jsonc, or checking for common Workers anti-patterns (streaming, floating promises, global state, secrets, bindings, observability). Biases towards retrieval from Cloudflare docs over pre-trained knowledge.
development
Review UI code for Web Interface Guidelines compliance. Use when asked to "review my UI", "check accessibility", "audit design", "review UX", or "check my site against best practices".
development
React and Next.js performance optimization guidelines from Vercel Engineering. This skill should be used when writing, reviewing, or refactoring React/Next.js code to ensure optimal performance patterns. Triggers on tasks involving React components, Next.js pages, data fetching, bundle optimization, or performance improvements.
development
React composition patterns that scale. Use when refactoring components with boolean prop proliferation, building flexible component libraries, or designing reusable APIs. Triggers on tasks involving compound components, render props, context providers, or component architecture. Includes React 19 API changes.