Owasp security
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.From its SKILL.md
npx -y skills add Makiya1202/ai-agents-skills --skill owasp-securityAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 2 stars2 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
13.4 KB, ~3.5k tokens by cl100k_base, as published. Nobody here has run it
OWASP Top 10 Security
Prevent common security vulnerabilities in web applications.
OWASP Top 10 (2021)
| # | 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 |
A01: Broken Access Control
Prevention Patterns
// ❌ 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);
Insecure Direct Object Reference (IDOR)
// ❌ 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);
});
A02: Cryptographic Failures
Password Hashing
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;
}
Secure Headers
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'"],
},
}));
A03: Injection
SQL Injection Prevention
// ❌ 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();
NoSQL Injection Prevention
// ❌ 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) });
// ...
});
Command Injection Prevention
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');
}
A04: Insecure Design
Rate Limiting
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);
Input Validation
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;
}
});
A05: Security Misconfiguration
Environment Configuration
// ✅ 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,
}));
A06: Vulnerable Components
Dependency Scanning
# 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
}
}
A07: Authentication Failures
Secure Session Management
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}`);
}
Multi-Factor Authentication
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 });
}
A08: XSS Prevention
// ✅ 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'"],
},
}));
A09: Logging & Monitoring
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' });
A10: SSRF Prevention
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);
// ...
});
Security Checklist
## 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
Resources
- OWASP Top 10: https://owasp.org/Top10/
- OWASP Cheat Sheets: https://cheatsheetseries.owasp.org/
- Node.js Security: https://nodejs.org/en/docs/guides/security/
- Snyk: https://snyk.io/
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.
Gives 1 of the 12 instructions most vulnerability scanning skills give in ~3.5k tokens
Counted across 223 of the 238 authors here whose files we hold, read 2026-09-06
- Fix critical findings immediatelyin 21 of 223, across 14 files
- Fix high findings before productionin 18 of 223, across 12 files
- Check and install AgentShield before scanningin 18 of 223, across 12 files
- Scaffold a secure configuration with initin 16 of 223, across 10 files
- Add the AgentShield GitHub Action to CIin 16 of 223, across 10 files
- Run the three-agent opus pipeline for deeper analysisin 14 of 223, across 8 files
- Apply safe auto-fixes onlyin 14 of 223, across 8 files
- Use JSON output for CI/CD integrationin 13 of 223, across 7 files
- Filter findings with a minimum severityin 12 of 223, across 10 files
- Classify each finding by severityin 10 of 223
- Use parameterized queries for all database accesshere, and in 10 of 223, across 9 files
- Write tests before writing the rulein 9 of 223, across 4 files
Said here and by no other author read
- Apply rate limiting to auth endpoints
- Sanitize user HTML before rendering
- Validate outbound URLs against allowlists
- Use short-lived access tokens
Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.