owasp-security

Comprehensive guide to preventing OWASP Top 10 vulnerabilities with code examples. Covers all 10 categories with prevention patterns: access control, cryptography, injection, design flaws, misconfiguration, vulnerable dependencies, authentication, data integrity, logging, and SSRF Includes production-ready TypeScript/Node.js code examples for each vulnerability type, from parameterized queries to secure password hashing and JWT token management Provides practical implementations for rate limiting, input validation with Zod, secure headers via Helmet, and multi-factor authentication setup Features a pre-deployment security checklist covering authentication, authorization, input validation, infrastructure hardening, and monitoring requirements

INSTALLATION
npx skills add https://github.com/hoodini/ai-agents-skills --skill owasp-security
Run in your project or agent environment. Adjust flags if your CLI version differs.

SKILL.md

$2a

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 &#x26; 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

BrowserAct

Let your agent run on any real-world website

Bypass CAPTCHA & anti-bot for free. Start local, scale to cloud.

Explore BrowserAct Skills →

Stop writing automation&scrapers

Install the CLI. Run your first Skill in 30 seconds. Scale when you're ready.

Start free
free · no credit card