security-checklist

SKILL.md

Security Checklist

This skill provides comprehensive security guidance to protect your applications from common vulnerabilities and attacks.

OWASP Top 10 Vulnerabilities

1. Broken Access Control

What it is: Users can access resources or perform actions they shouldn't be authorized for.

Examples:

  • Accessing another user's data by changing URL parameter
  • Elevating privileges (user → admin)
  • Bypassing authentication checks

Prevention:

// ❌ BAD - No authorization check
app.get('/api/users/:id', async (req, res) => {
  const user = await db.user.findUnique({ where: { id: req.params.id } });
  res.json(user);
});

// ✅ GOOD - Verify ownership or admin
app.get('/api/users/:id', authenticate, async (req, res) => {
  const requestedId = req.params.id;
  const currentUserId = req.user.id;
  const isAdmin = req.user.role === 'admin';

  if (requestedId !== currentUserId && !isAdmin) {
    return res.status(403).json({ error: 'Forbidden' });
  }

  const user = await db.user.findUnique({ where: { id: requestedId } });
  res.json(user);
});

Checklist:

  • Enforce least privilege (deny by default)
  • Verify user permissions on every request
  • Never trust user IDs from client
  • Log access control failures
  • Use centralized access control logic

2. Cryptographic Failures

What it is: Exposing sensitive data due to weak or missing encryption.

Examples:

  • Storing passwords in plain text
  • Using weak hashing algorithms (MD5, SHA1)
  • Transmitting sensitive data over HTTP

Prevention:

import bcrypt from 'bcrypt';
import crypto from 'crypto';

// ✅ Password hashing
async function hashPassword(password: string): Promise<string> {
  const saltRounds = 12; // Increase for more security
  return await bcrypt.hash(password, saltRounds);
}

async function verifyPassword(password: string, hash: string): Promise<boolean> {
  return await bcrypt.compare(password, hash);
}

// ✅ Encrypt sensitive data at rest
function encrypt(text: string, key: string): string {
  const iv = crypto.randomBytes(16);
  const cipher = crypto.createCipheriv('aes-256-gcm', Buffer.from(key), iv);

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

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

Checklist:

  • Use HTTPS everywhere
  • Hash passwords with bcrypt (12+ rounds)
  • Encrypt sensitive data at rest
  • Use strong encryption algorithms (AES-256)
  • Properly manage encryption keys
  • Never commit secrets to version control

3. Injection Attacks

See SQL Injection Prevention section below

4. Insecure Design

What it is: Security flaws in the application architecture.

Examples:

  • Missing rate limiting
  • Unrestricted file uploads
  • Insecure password reset flows

Prevention:

// ✅ 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, please try again later',
});

app.post('/api/auth/login', loginLimiter, async (req, res) => {
  // Login logic
});

// ✅ Secure file upload
import multer from 'multer';

const upload = multer({
  limits: {
    fileSize: 5 * 1024 * 1024, // 5MB max
  },
  fileFilter: (req, file, cb) => {
    const allowedTypes = ['image/jpeg', 'image/png', 'image/gif'];
    if (!allowedTypes.includes(file.mimetype)) {
      return cb(new Error('Invalid file type'));
    }
    cb(null, true);
  },
});

Checklist:

  • Implement rate limiting on all endpoints
  • Validate file uploads (type, size, content)
  • Use secure password reset flows (token-based)
  • Implement account lockout after failed attempts
  • Log and monitor security events

5. Security Misconfiguration

What it is: Insecure default configurations, unnecessary features enabled.

Prevention:

// ✅ Secure Express configuration
import express from 'express';
import helmet from 'helmet';

const app = express();

// Security headers
app.use(helmet());

// Disable X-Powered-By
app.disable('x-powered-by');

// Environment-specific settings
if (process.env.NODE_ENV === 'production') {
  app.set('trust proxy', 1); // Trust first proxy
}

// CORS configuration
import cors from 'cors';

const corsOptions = {
  origin: process.env.ALLOWED_ORIGINS?.split(',') || [],
  credentials: true,
  optionsSuccessStatus: 200,
};

app.use(cors(corsOptions));

Checklist:

  • Remove default accounts and passwords
  • Disable directory listing
  • Remove unnecessary features/endpoints
  • Keep all software updated
  • Use security headers (see section below)

6. Vulnerable and Outdated Components

Prevention:

# Check for vulnerabilities
npm audit

# Fix vulnerabilities
npm audit fix

# Update dependencies
npm update

# Use automated tools
npm install -g snyk
snyk test
snyk monitor

Checklist:

  • Regularly update dependencies
  • Monitor security advisories
  • Remove unused dependencies
  • Use dependabot or renovate
  • Pin dependency versions in production

7. Identification and Authentication Failures

See Authentication Best Practices section below

8. Software and Data Integrity Failures

Prevention:

// ✅ Verify package integrity
// package-lock.json ensures same versions

// ✅ Use Subresource Integrity (SRI) for CDN
<script
  src="https://cdn.example.com/library.js"
  integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
  crossorigin="anonymous"
></script>

// ✅ Validate data from external sources
import { z } from 'zod';

const ExternalDataSchema = z.object({
  id: z.string().uuid(),
  amount: z.number().positive(),
  status: z.enum(['pending', 'completed', 'failed']),
});

function processExternalData(data: unknown) {
  const validated = ExternalDataSchema.parse(data);
  // Now safe to use
}

9. Security Logging and Monitoring Failures

Prevention:

import winston from 'winston';

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

// ✅ Log security events
logger.warn('Failed login attempt', {
  email: req.body.email,
  ip: req.ip,
  userAgent: req.get('user-agent'),
  timestamp: new Date().toISOString(),
});

logger.error('SQL injection attempt detected', {
  query: sanitizedQuery,
  ip: req.ip,
  endpoint: req.path,
});

Checklist:

  • Log authentication events (login, logout, failures)
  • Log authorization failures
  • Log input validation failures
  • Monitor for suspicious patterns
  • Set up alerts for security events

10. Server-Side Request Forgery (SSRF)

Prevention:

import validator from 'validator';

// ❌ BAD - Allows SSRF
app.post('/api/fetch-url', async (req, res) => {
  const url = req.body.url;
  const response = await fetch(url);
  res.json(await response.json());
});

// ✅ GOOD - Validate and whitelist
app.post('/api/fetch-url', async (req, res) => {
  const url = req.body.url;

  // Validate URL format
  if (!validator.isURL(url, { protocols: ['https'] })) {
    return res.status(400).json({ error: 'Invalid URL' });
  }

  // Whitelist allowed domains
  const allowedDomains = ['api.trusted-service.com'];
  const urlObj = new URL(url);

  if (!allowedDomains.includes(urlObj.hostname)) {
    return res.status(403).json({ error: 'Domain not allowed' });
  }

  // Block internal IPs
  const hostname = urlObj.hostname;
  if (
    hostname === 'localhost' ||
    hostname.startsWith('192.168.') ||
    hostname.startsWith('10.') ||
    hostname.startsWith('172.')
  ) {
    return res.status(403).json({ error: 'Internal IPs not allowed' });
  }

  const response = await fetch(url);
  res.json(await response.json());
});

SQL Injection Prevention

Parameterized Queries

// ❌ VULNERABLE - String concatenation
const email = req.body.email;
const query = `SELECT * FROM users WHERE email = '${email}'`;
// Attacker can input: ' OR '1'='1
// Resulting query: SELECT * FROM users WHERE email = '' OR '1'='1'

// ✅ SAFE - Parameterized query
const email = req.body.email;
const query = 'SELECT * FROM users WHERE email = ?';
const user = await db.execute(query, [email]);

// ✅ SAFE - ORM (Prisma)
const user = await prisma.user.findUnique({
  where: { email: req.body.email },
});

Input Validation

import { z } from 'zod';

const UserQuerySchema = z.object({
  email: z.string().email(),
  id: z.string().uuid().optional(),
  name: z.string().max(100).optional(),
});

app.get('/api/users', async (req, res) => {
  try {
    const params = UserQuerySchema.parse(req.query);
    const users = await db.user.findMany({ where: params });
    res.json(users);
  } catch (error) {
    res.status(400).json({ error: 'Invalid query parameters' });
  }
});

XSS (Cross-Site Scripting) Prevention

Types of XSS

1. Stored XSS: Malicious script stored in database 2. Reflected XSS: Malicious script in URL/input reflected back 3. DOM-based XSS: Script manipulates DOM directly

Prevention

// ✅ React automatically escapes by default
function Comment({ text }: { text: string }) {
  return <p>{text}</p>; // Safe - React escapes HTML
}

// ❌ DANGEROUS - dangerouslySetInnerHTML
function Comment({ html }: { html: string }) {
  return <p dangerouslySetInnerHTML={{ __html: html }} />; // UNSAFE
}

// ✅ Sanitize HTML before rendering
import DOMPurify from 'dompurify';

function Comment({ html }: { html: string }) {
  const clean = DOMPurify.sanitize(html, {
    ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a'],
    ALLOWED_ATTR: ['href'],
  });
  return <p dangerouslySetInnerHTML={{ __html: clean }} />;
}

// ✅ Server-side sanitization
import sanitizeHtml from 'sanitize-html';

app.post('/api/comments', async (req, res) => {
  const clean = sanitizeHtml(req.body.content, {
    allowedTags: ['b', 'i', 'em', 'strong', 'a'],
    allowedAttributes: {
      'a': ['href'],
    },
  });

  const comment = await db.comment.create({
    data: { content: clean },
  });

  res.json(comment);
});

Content Security Policy (CSP)

app.use(
  helmet.contentSecurityPolicy({
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'", 'https://trusted-cdn.com'],
      styleSrc: ["'self'", "'unsafe-inline'"],
      imgSrc: ["'self'", 'data:', 'https:'],
      connectSrc: ["'self'", 'https://api.example.com'],
      fontSrc: ["'self'", 'https://fonts.gstatic.com'],
      objectSrc: ["'none'"],
      upgradeInsecureRequests: [],
    },
  })
);

CSRF (Cross-Site Request Forgery) Protection

CSRF Token Pattern

import csrf from 'csurf';
import cookieParser from 'cookie-parser';

const csrfProtection = csrf({ cookie: true });

app.use(cookieParser());

// Get CSRF token
app.get('/api/csrf-token', csrfProtection, (req, res) => {
  res.json({ csrfToken: req.csrfToken() });
});

// Protect state-changing endpoints
app.post('/api/users', csrfProtection, async (req, res) => {
  // Token automatically verified by middleware
  const user = await createUser(req.body);
  res.json(user);
});

SameSite Cookies

// ✅ Set SameSite attribute
res.cookie('sessionId', token, {
  httpOnly: true,
  secure: true, // HTTPS only
  sameSite: 'strict', // or 'lax'
  maxAge: 24 * 60 * 60 * 1000, // 24 hours
});

Authentication Best Practices

Password Requirements

import { z } from 'zod';

const PasswordSchema = z
  .string()
  .min(12, 'Password must be at least 12 characters')
  .regex(/[A-Z]/, 'Password must contain uppercase letter')
  .regex(/[a-z]/, 'Password must contain lowercase letter')
  .regex(/[0-9]/, 'Password must contain number')
  .regex(/[^A-Za-z0-9]/, 'Password must contain special character');

// Check against common passwords
import { isCommonPassword } from 'common-passwords';

function validatePassword(password: string): boolean {
  if (isCommonPassword(password)) {
    throw new Error('Password is too common');
  }
  return true;
}

JWT Best Practices

import jwt from 'jsonwebtoken';

// ✅ Short-lived access tokens
function generateAccessToken(userId: string): string {
  return jwt.sign(
    { userId, type: 'access' },
    process.env.JWT_SECRET!,
    { expiresIn: '15m' } // Short expiry
  );
}

// ✅ Long-lived refresh tokens
function generateRefreshToken(userId: string): string {
  return jwt.sign(
    { userId, type: 'refresh' },
    process.env.JWT_REFRESH_SECRET!,
    { expiresIn: '7d' }
  );
}

// ✅ Verify token
function verifyAccessToken(token: string) {
  try {
    const payload = jwt.verify(token, process.env.JWT_SECRET!);
    if (payload.type !== 'access') {
      throw new Error('Invalid token type');
    }
    return payload;
  } catch (error) {
    throw new Error('Invalid token');
  }
}

// ✅ Blacklist tokens on logout
const tokenBlacklist = new Set<string>();

app.post('/api/auth/logout', authenticate, (req, res) => {
  const token = req.headers.authorization?.split(' ')[1];
  if (token) {
    tokenBlacklist.add(token);
  }
  res.json({ message: 'Logged out' });
});

Multi-Factor Authentication (MFA)

import speakeasy from 'speakeasy';
import QRCode from 'qrcode';

// Generate MFA secret
async function enableMFA(userId: string) {
  const secret = speakeasy.generateSecret({
    name: `MyApp (${user.email})`,
  });

  // Generate QR code
  const qrCode = await QRCode.toDataURL(secret.otpauth_url!);

  // Save secret to database (encrypted)
  await db.user.update({
    where: { id: userId },
    data: { mfaSecret: encrypt(secret.base32) },
  });

  return { secret: secret.base32, qrCode };
}

// Verify MFA token
function verifyMFAToken(secret: string, token: string): boolean {
  return speakeasy.totp.verify({
    secret,
    encoding: 'base32',
    token,
    window: 2, // Allow 2 time steps before/after
  });
}

Authorization Patterns

Role-Based Access Control (RBAC)

enum Role {
  USER = 'user',
  MODERATOR = 'moderator',
  ADMIN = 'admin',
}

const permissions = {
  [Role.USER]: ['read:own', 'write:own'],
  [Role.MODERATOR]: ['read:own', 'write:own', 'read:all', 'delete:others'],
  [Role.ADMIN]: ['*'], // All permissions
};

function authorize(requiredPermission: string) {
  return (req: Request, res: Response, next: NextFunction) => {
    const userRole = req.user.role;
    const userPermissions = permissions[userRole];

    if (!userPermissions.includes('*') && !userPermissions.includes(requiredPermission)) {
      return res.status(403).json({ error: 'Forbidden' });
    }

    next();
  };
}

// Usage
app.delete('/api/posts/:id', authenticate, authorize('delete:others'), async (req, res) => {
  await deletePost(req.params.id);
  res.json({ success: true });
});

Attribute-Based Access Control (ABAC)

interface AccessPolicy {
  subject: { role: string; department?: string };
  resource: { type: string; owner?: string };
  action: string;
  conditions?: Record<string, any>;
}

function checkAccess(
  user: User,
  resource: Resource,
  action: string
): boolean {
  // User can access own resources
  if (resource.ownerId === user.id) {
    return true;
  }

  // Admins can access everything
  if (user.role === 'admin') {
    return true;
  }

  // Department managers can access department resources
  if (
    user.role === 'manager' &&
    user.department === resource.department
  ) {
    return true;
  }

  return false;
}

Secrets Management

Environment Variables

// ✅ .env file (NOT committed to git)
DATABASE_URL=postgresql://user:password@localhost:5432/db
JWT_SECRET=super-secret-key-change-in-production
API_KEY=sk_live_abc123xyz

// ✅ .env.example (committed to git)
DATABASE_URL=
JWT_SECRET=
API_KEY=

// ✅ Load environment variables
import dotenv from 'dotenv';
dotenv.config();

// ✅ Validate required env vars
const requiredEnvVars = ['DATABASE_URL', 'JWT_SECRET', 'API_KEY'];

for (const envVar of requiredEnvVars) {
  if (!process.env[envVar]) {
    throw new Error(`Missing required environment variable: ${envVar}`);
  }
}

Key Vault Integration

// ✅ AWS Secrets Manager
import { SecretsManager } from '@aws-sdk/client-secrets-manager';

async function getSecret(secretName: string): Promise<string> {
  const client = new SecretsManager({ region: 'us-east-1' });
  const response = await client.getSecretValue({ SecretId: secretName });
  return response.SecretString!;
}

// ✅ Azure Key Vault
import { SecretClient } from '@azure/keyvault-secrets';

async function getAzureSecret(secretName: string): Promise<string> {
  const client = new SecretClient(vaultUrl, credential);
  const secret = await client.getSecret(secretName);
  return secret.value!;
}

Input Validation

Validation Schema

import { z } from 'zod';

const CreateUserSchema = z.object({
  email: z.string().email().max(255),
  password: z.string().min(12).max(100),
  name: z.string().min(1).max(100),
  age: z.number().int().min(18).max(120),
  phone: z.string().regex(/^\+?[1-9]\d{1,14}$/).optional(),
  website: z.string().url().optional(),
});

app.post('/api/users', async (req, res) => {
  try {
    const data = CreateUserSchema.parse(req.body);
    const user = await createUser(data);
    res.json(user);
  } catch (error) {
    if (error instanceof z.ZodError) {
      return res.status(400).json({
        error: 'Validation failed',
        details: error.errors,
      });
    }
    throw error;
  }
});

Sanitization

import validator from 'validator';

function sanitizeInput(input: string): string {
  // Trim whitespace
  let clean = input.trim();

  // Escape HTML entities
  clean = validator.escape(clean);

  // Remove null bytes
  clean = clean.replace(/\0/g, '');

  return clean;
}

Security Headers

Comprehensive Header Configuration

import helmet from 'helmet';

app.use(
  helmet({
    // Content Security Policy
    contentSecurityPolicy: {
      directives: {
        defaultSrc: ["'self'"],
        scriptSrc: ["'self'", "'unsafe-inline'"],
        styleSrc: ["'self'", "'unsafe-inline'"],
        imgSrc: ["'self'", 'data:', 'https:'],
      },
    },

    // HTTP Strict Transport Security
    hsts: {
      maxAge: 31536000, // 1 year
      includeSubDomains: true,
      preload: true,
    },

    // X-Frame-Options
    frameguard: {
      action: 'deny', // or 'sameorigin'
    },

    // X-Content-Type-Options
    noSniff: true,

    // X-XSS-Protection
    xssFilter: true,

    // Referrer-Policy
    referrerPolicy: {
      policy: 'strict-origin-when-cross-origin',
    },
  })
);

// Additional custom headers
app.use((req, res, next) => {
  res.setHeader('X-Content-Type-Options', 'nosniff');
  res.setHeader('X-Frame-Options', 'DENY');
  res.setHeader('X-XSS-Protection', '1; mode=block');
  res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');
  next();
});

Rate Limiting

Endpoint-Specific Rate Limits

import rateLimit from 'express-rate-limit';

// Strict limit for authentication
const authLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 5,
  message: 'Too many authentication attempts',
  standardHeaders: true,
  legacyHeaders: false,
});

// Moderate limit for API endpoints
const apiLimiter = rateLimit({
  windowMs: 1 * 60 * 1000, // 1 minute
  max: 60,
  message: 'Too many requests',
});

// Apply rate limiters
app.post('/api/auth/login', authLimiter, loginHandler);
app.post('/api/auth/register', authLimiter, registerHandler);
app.use('/api', apiLimiter);

Logging Security Events

What NOT to Log

❌ Never log:

  • Passwords (plain or hashed)
  • Credit card numbers
  • Social Security numbers
  • API keys/secrets
  • Session tokens
  • Personally identifiable information (PII) in clear text
// ❌ BAD - Logs sensitive data
logger.info('User login', { email, password });

// ✅ GOOD - Logs non-sensitive data
logger.info('User login attempt', {
  email,
  ip: req.ip,
  userAgent: req.get('user-agent'),
  success: true,
});

Security Event Logging

enum SecurityEvent {
  LOGIN_SUCCESS = 'login_success',
  LOGIN_FAILURE = 'login_failure',
  PASSWORD_RESET = 'password_reset',
  ACCOUNT_LOCKED = 'account_locked',
  PERMISSION_DENIED = 'permission_denied',
  SUSPICIOUS_ACTIVITY = 'suspicious_activity',
}

function logSecurityEvent(
  event: SecurityEvent,
  userId: string | null,
  metadata: Record<string, any>
) {
  logger.warn({
    type: 'security',
    event,
    userId,
    timestamp: new Date().toISOString(),
    ip: metadata.ip,
    userAgent: metadata.userAgent,
    ...metadata,
  });
}

Dependency Scanning

Automated Security Scanning

# .github/workflows/security.yml
name: Security Scan

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
  schedule:
    - cron: '0 0 * * 0' # Weekly

jobs:
  security:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Run npm audit
        run: npm audit --audit-level=high

      - name: Run Snyk security scan
        uses: snyk/actions/node@master
        env:
          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}

      - name: Run Trivy vulnerability scanner
        uses: aquasecurity/trivy-action@master
        with:
          scan-type: 'fs'
          scan-ref: '.'

Security Checklist Summary

Pre-Deployment Checklist

Authentication & Authorization:

  • Passwords hashed with bcrypt (12+ rounds)
  • JWT tokens have short expiry (15 minutes)
  • Refresh tokens properly implemented
  • MFA available for sensitive accounts
  • Authorization checks on all endpoints
  • Rate limiting on auth endpoints

Input Validation:

  • All inputs validated with schemas
  • SQL queries use parameterized statements
  • File uploads restricted (type, size)
  • HTML content sanitized
  • No eval() or Function() with user input

Security Headers:

  • HTTPS enforced
  • CSP configured
  • HSTS enabled
  • X-Frame-Options set
  • X-Content-Type-Options set

Data Protection:

  • Sensitive data encrypted at rest
  • Secrets stored in environment variables/vault
  • No secrets in version control
  • PII handled according to regulations
  • Database backups encrypted

Logging & Monitoring:

  • Security events logged
  • No sensitive data in logs
  • Failed auth attempts monitored
  • Unusual activity alerts configured
  • Log retention policy defined

Dependencies:

  • All dependencies up to date
  • No known vulnerabilities (npm audit)
  • Dependabot/Renovate configured
  • Unused dependencies removed

API Security:

  • CORS properly configured
  • CSRF protection enabled
  • Rate limiting implemented
  • API keys rotated regularly
  • Endpoints documented

When to Use This Skill

Use this skill when:

  • Conducting security audits
  • Implementing authentication/authorization
  • Reviewing code for vulnerabilities
  • Setting up new projects
  • Handling sensitive data
  • Responding to security incidents
  • Training team on security
  • Preparing for compliance audits
  • Deploying to production
  • Integrating third-party services

Remember: Security is not a one-time task but an ongoing process. Stay informed about new vulnerabilities, keep dependencies updated, and always assume malicious actors are trying to exploit your application.

Weekly Installs
2
GitHub Stars
6
First Seen
13 days ago
Installed on
openclaw2
gemini-cli2
github-copilot2
codex2
kimi-cli2
cursor2