security

SKILL.md

Web Application Security

Security best practices and vulnerability prevention.

OWASP Top 10

1. Injection (SQL, NoSQL, Command)

// BAD: SQL Injection
const query = `SELECT * FROM users WHERE email = '${email}'`;
db.query(query);

// GOOD: Parameterized queries
const query = 'SELECT * FROM users WHERE email = ?';
db.query(query, [email]);

// GOOD: Using ORM
const user = await User.findOne({ where: { email } });

// BAD: Command injection
const output = execSync(`ls ${userInput}`);

// GOOD: Avoid shell, use array
const output = execFileSync('ls', [sanitizedPath]);

// BAD: NoSQL injection
db.users.find({ username: req.body.username, password: req.body.password });

// GOOD: Type validation
const username = String(req.body.username);
const password = String(req.body.password);
db.users.find({ username, password });

2. Broken Authentication

// Password hashing
import bcrypt from 'bcrypt';

const SALT_ROUNDS = 12;

async function hashPassword(password) {
    return bcrypt.hash(password, SALT_ROUNDS);
}

async function verifyPassword(password, hash) {
    return bcrypt.compare(password, hash);
}

// Session management
import session from 'express-session';
import RedisStore from 'connect-redis';

app.use(session({
    store: new RedisStore({ client: redisClient }),
    secret: process.env.SESSION_SECRET,
    resave: false,
    saveUninitialized: false,
    cookie: {
        secure: true,           // HTTPS only
        httpOnly: true,         // No JavaScript access
        sameSite: 'strict',     // CSRF protection
        maxAge: 3600000,        // 1 hour
    },
}));

// Rate limiting
import rateLimit from 'express-rate-limit';

const loginLimiter = rateLimit({
    windowMs: 15 * 60 * 1000,   // 15 minutes
    max: 5,                     // 5 attempts
    message: 'Too many login attempts, try again later',
    standardHeaders: true,
    legacyHeaders: false,
});

app.post('/login', loginLimiter, loginHandler);

3. Sensitive Data Exposure

// Never log sensitive data
// BAD
console.log('User login:', { email, password });

// GOOD
console.log('User login:', { email, password: '[REDACTED]' });

// Encrypt sensitive data at rest
import crypto from 'crypto';

const ENCRYPTION_KEY = Buffer.from(process.env.ENCRYPTION_KEY, 'hex');
const IV_LENGTH = 16;

function encrypt(text) {
    const iv = crypto.randomBytes(IV_LENGTH);
    const cipher = crypto.createCipheriv('aes-256-gcm', ENCRYPTION_KEY, iv);

    let encrypted = cipher.update(text, 'utf8', 'hex');
    encrypted += cipher.final('hex');

    const authTag = cipher.getAuthTag();

    return `${iv.toString('hex')}:${authTag.toString('hex')}:${encrypted}`;
}

function decrypt(encryptedData) {
    const [ivHex, authTagHex, encrypted] = encryptedData.split(':');
    const iv = Buffer.from(ivHex, 'hex');
    const authTag = Buffer.from(authTagHex, 'hex');

    const decipher = crypto.createDecipheriv('aes-256-gcm', ENCRYPTION_KEY, iv);
    decipher.setAuthTag(authTag);

    let decrypted = decipher.update(encrypted, 'hex', 'utf8');
    decrypted += decipher.final('utf8');

    return decrypted;
}

4. XML External Entities (XXE)

// BAD: Default parser may be vulnerable
const parser = new DOMParser();
const doc = parser.parseFromString(xmlString, 'text/xml');

// GOOD: Disable external entities
import { XMLParser } from 'fast-xml-parser';

const parser = new XMLParser({
    allowBooleanAttributes: true,
    ignoreAttributes: false,
    // Disable external entities and DTD processing
});

const result = parser.parse(xmlString);

5. Broken Access Control

// IDOR Prevention
// BAD: Direct object reference
app.get('/api/orders/:id', async (req, res) => {
    const order = await Order.findById(req.params.id);
    res.json(order);  // Any user can access any order!
});

// GOOD: Verify ownership
app.get('/api/orders/:id', async (req, res) => {
    const order = await Order.findOne({
        _id: req.params.id,
        userId: req.user.id,  // Only owner's orders
    });

    if (!order) {
        return res.status(404).json({ error: 'Not found' });
    }

    res.json(order);
});

// Role-based access control
function requireRole(...roles) {
    return (req, res, next) => {
        if (!req.user || !roles.includes(req.user.role)) {
            return res.status(403).json({ error: 'Forbidden' });
        }
        next();
    };
}

app.delete('/api/users/:id', requireRole('admin'), deleteUser);

6. Security Misconfiguration

// Security headers
import helmet from 'helmet';

app.use(helmet({
    contentSecurityPolicy: {
        directives: {
            defaultSrc: ["'self'"],
            scriptSrc: ["'self'", "'unsafe-inline'"],  // Avoid if possible
            styleSrc: ["'self'", "'unsafe-inline'"],
            imgSrc: ["'self'", 'data:', 'https:'],
            connectSrc: ["'self'", 'https://api.example.com'],
            frameSrc: ["'none'"],
            objectSrc: ["'none'"],
        },
    },
    crossOriginEmbedderPolicy: true,
    crossOriginOpenerPolicy: true,
    crossOriginResourcePolicy: { policy: 'same-site' },
    hsts: {
        maxAge: 31536000,
        includeSubDomains: true,
        preload: true,
    },
}));

// Disable server info
app.disable('x-powered-by');

// Error handling - don't leak stack traces
app.use((err, req, res, next) => {
    console.error(err.stack);  // Log full error

    res.status(500).json({
        error: process.env.NODE_ENV === 'production'
            ? 'Internal server error'
            : err.message,
    });
});

7. Cross-Site Scripting (XSS)

// Input sanitization
import DOMPurify from 'dompurify';
import { JSDOM } from 'jsdom';

const window = new JSDOM('').window;
const purify = DOMPurify(window);

// Sanitize HTML input
const cleanHtml = purify.sanitize(userInput);

// Output encoding
import { encode } from 'html-entities';

const safeOutput = encode(userInput);

// React automatically escapes
function Comment({ text }) {
    return <p>{text}</p>;  // Safe - React escapes
}

// BAD: dangerouslySetInnerHTML
<div dangerouslySetInnerHTML={{ __html: userInput }} />  // XSS risk!

// GOOD: sanitize first
<div dangerouslySetInnerHTML={{ __html: purify.sanitize(userInput) }} />

8. Insecure Deserialization

// BAD: Deserializing untrusted data
const data = JSON.parse(userInput);
eval(data.callback);  // Remote code execution!

// GOOD: Validate schema
import Ajv from 'ajv';

const ajv = new Ajv();
const schema = {
    type: 'object',
    properties: {
        name: { type: 'string', maxLength: 100 },
        age: { type: 'integer', minimum: 0, maximum: 150 },
    },
    required: ['name'],
    additionalProperties: false,
};

const validate = ajv.compile(schema);
const data = JSON.parse(userInput);

if (!validate(data)) {
    throw new Error('Invalid data');
}

9. Using Components with Known Vulnerabilities

# Check for vulnerabilities
npm audit
npm audit fix

# Use Snyk for deeper analysis
npx snyk test

# Keep dependencies updated
npx npm-check-updates -u

# Lock file for reproducible builds
npm ci  # Use in CI/CD

10. Insufficient Logging & Monitoring

// Security event logging
import winston from 'winston';

const securityLogger = winston.createLogger({
    level: 'info',
    format: winston.format.json(),
    transports: [
        new winston.transports.File({ filename: 'security.log' }),
    ],
});

// Log security events
function logSecurityEvent(event, details) {
    securityLogger.info({
        timestamp: new Date().toISOString(),
        event,
        ...details,
        ip: details.req?.ip,
        userAgent: details.req?.get('user-agent'),
    });
}

// Usage
app.post('/login', async (req, res) => {
    try {
        const user = await authenticate(req.body);

        logSecurityEvent('LOGIN_SUCCESS', {
            req,
            userId: user.id,
        });

        // ...
    } catch (error) {
        logSecurityEvent('LOGIN_FAILURE', {
            req,
            email: req.body.email,
            reason: error.message,
        });

        // ...
    }
});

Authentication

JWT Best Practices

import jwt from 'jsonwebtoken';

const ACCESS_TOKEN_SECRET = process.env.ACCESS_TOKEN_SECRET;
const REFRESH_TOKEN_SECRET = process.env.REFRESH_TOKEN_SECRET;

// Short-lived access token
function generateAccessToken(user) {
    return jwt.sign(
        { userId: user.id, role: user.role },
        ACCESS_TOKEN_SECRET,
        { expiresIn: '15m', algorithm: 'HS256' }
    );
}

// Long-lived refresh token
function generateRefreshToken(user) {
    return jwt.sign(
        { userId: user.id, tokenVersion: user.tokenVersion },
        REFRESH_TOKEN_SECRET,
        { expiresIn: '7d', algorithm: 'HS256' }
    );
}

// Verify middleware
function authenticate(req, res, next) {
    const authHeader = req.headers.authorization;

    if (!authHeader?.startsWith('Bearer ')) {
        return res.status(401).json({ error: 'Missing token' });
    }

    const token = authHeader.split(' ')[1];

    try {
        const payload = jwt.verify(token, ACCESS_TOKEN_SECRET);
        req.user = payload;
        next();
    } catch (error) {
        if (error.name === 'TokenExpiredError') {
            return res.status(401).json({ error: 'Token expired' });
        }
        return res.status(403).json({ error: 'Invalid token' });
    }
}

OAuth 2.0 / OIDC

import { Issuer, generators } from 'openid-client';

// Configure client
const issuer = await Issuer.discover('https://accounts.google.com');
const client = new issuer.Client({
    client_id: process.env.GOOGLE_CLIENT_ID,
    client_secret: process.env.GOOGLE_CLIENT_SECRET,
    redirect_uris: ['https://example.com/callback'],
    response_types: ['code'],
});

// Generate authorization URL
app.get('/auth/google', (req, res) => {
    const codeVerifier = generators.codeVerifier();
    const codeChallenge = generators.codeChallenge(codeVerifier);

    req.session.codeVerifier = codeVerifier;
    req.session.state = generators.state();

    const url = client.authorizationUrl({
        scope: 'openid email profile',
        code_challenge: codeChallenge,
        code_challenge_method: 'S256',
        state: req.session.state,
    });

    res.redirect(url);
});

// Handle callback
app.get('/callback', async (req, res) => {
    const params = client.callbackParams(req);
    const tokenSet = await client.callback(
        'https://example.com/callback',
        params,
        {
            code_verifier: req.session.codeVerifier,
            state: req.session.state,
        }
    );

    const userInfo = await client.userinfo(tokenSet.access_token);
    // Create session, redirect user
});

Input Validation

import { z } from 'zod';

// Define schema
const userSchema = z.object({
    email: z.string().email().max(255),
    password: z.string().min(8).max(100),
    name: z.string().min(1).max(100).regex(/^[a-zA-Z\s]+$/),
    age: z.number().int().min(13).max(150).optional(),
});

// Validate
function validateInput(schema) {
    return (req, res, next) => {
        try {
            req.validated = schema.parse(req.body);
            next();
        } catch (error) {
            res.status(400).json({
                error: 'Validation failed',
                details: error.errors,
            });
        }
    };
}

app.post('/users', validateInput(userSchema), createUser);

CSRF Protection

import csrf from 'csurf';

// For traditional forms
const csrfProtection = csrf({ cookie: true });

app.get('/form', csrfProtection, (req, res) => {
    res.render('form', { csrfToken: req.csrfToken() });
});

// For SPAs - use SameSite cookies + custom header
// Client sends: X-Requested-With: XMLHttpRequest
app.use((req, res, next) => {
    if (req.method !== 'GET' && req.method !== 'HEAD') {
        if (req.headers['x-requested-with'] !== 'XMLHttpRequest') {
            return res.status(403).json({ error: 'CSRF check failed' });
        }
    }
    next();
});

Security Checklist

Application

  • Use HTTPS everywhere
  • Validate all input (whitelist approach)
  • Encode all output
  • Use parameterized queries
  • Implement proper authentication
  • Implement proper authorization
  • Hash passwords with bcrypt/argon2
  • Use secure session management
  • Set security headers (helmet)
  • Implement rate limiting
  • Log security events
  • Handle errors without leaking info

Infrastructure

  • Keep dependencies updated
  • Use secrets management
  • Configure firewalls
  • Enable audit logging
  • Set up intrusion detection
  • Regular security scans
  • Backup encryption
  • Least privilege access

Development

  • Security code reviews
  • Static analysis (SAST)
  • Dynamic analysis (DAST)
  • Dependency scanning
  • Security training
  • Incident response plan
Weekly Installs
8
First Seen
Jan 20, 2026
Installed on
claude-code7
windsurf6
codex6
gemini-cli6
opencode5
antigravity5