Files
dwroller/docs/security.md
alex a4cabbd2b8 Clean rules database and add documentation
- Remove OCR noise, credits, and duplicates from rules-database.json (288→255 rules)
- Add clean_rules.py script for rule cleanup
- Add CLAUDE.md, docs/, and update README with documentation links

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 17:35:36 +02:00

5.6 KiB

Security Guide

This document outlines the security measures and best practices for Deathwatch Roller.

Authentication

Session Management

  • Sessions use HTTP-only cookies
  • Sessions expire after inactivity
  • Passwords are hashed with bcrypt
  • XSS protection headers enabled

Session Configuration

// Example session config
session: {
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false,
  cookie: {
    httpOnly: true,
    secure: process.env.NODE_ENV === 'production',
    maxAge: 24 * 60 * 60 * 1000 // 24 hours
  }
}

Database Security

Password Hashing

// Password hashing
import bcrypt from 'bcrypt';

const hashedPassword = await bcrypt.hash(password, 12);
const isMatch = await bcrypt.compare(inputPassword, hashedPassword);

Prepared Statements

All database queries use parameterized statements to prevent SQL injection:

// ✅ GOOD - Using parameterized query
const player = await Player.findOne({
  where: { email: email },
  raw: true
});

// ❌ BAD - String concatenation
const badQuery = `SELECT * FROM players WHERE email = '${email}'`;

Input Validation

Request Validation

All API requests are validated:

import { body, validationResult } from 'express-validator';

router.post('/players', [
  body('name').isLength({ min: 1, max: 50 }).trim(),
  body('email').isEmail().normalizeEmail(),
  body('xp').isInt({ min: 0 })
], async (req, res) => {
  const errors = validationResult(req);
  if (!errors.isEmpty()) {
    return res.status(400).json({ errors: errors.array() });
  }
  // Process request
});

CORS Configuration

// CORS setup
import cors from 'cors';

app.use(cors({
  origin: process.env.ALLOWED_ORIGINS?.split(','),
  credentials: true,
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization']
}));

Rate Limiting

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

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // limit each IP to 100 requests per windowMs
  message: 'Too many requests from this IP, please try again later.'
});

app.use('/api/', limiter);

Security Headers

import helmet from 'helmet';

app.use(helmet());

// 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');
  next();
});

Environment Variables

Store sensitive data in environment variables:

# .env file
SESSION_SECRET=your-secure-random-string
X_GM_SECRET=your-gm-secret-key
DATABASE_PASSWORD=your-database-password

Never commit .env files:

# .gitignore
.env
.env.*
!/.env.example

API Security

GM Authentication

GM endpoints require special authentication:

// GM secret header
app.use('/api/players/gm/*', (req, res, next) => {
  const secret = req.headers['x-gm-secret'];
  if (!secret || secret !== process.env.X_GM_SECRET) {
    return res.status(403).json({ error: 'Forbidden' });
  }
  next();
});

API Rate Limits

const apiLimiter = rateLimit({
  windowMs: 60 * 1000, // 1 minute
  max: 100,
  keyGenerator: (req) => req.ip
});

app.use('/api/', apiLimiter);

File Upload Security

import multer from 'multer';

const upload = multer({
  storage: multer.diskStorage({
    destination: (req, file, cb) => cb(null, 'uploads/'),
    filename: (req, file, cb) => {
      const uniqueName = `${Date.now()}-${Math.random()}-${file.originalname}`;
      cb(null, uniqueName);
    }
  }),
  fileFilter: (req, file, cb) => {
    const allowedTypes = ['image/jpeg', 'image/png', 'image/gif'];
    if (allowedTypes.includes(file.mimetype)) {
      cb(null, true);
    } else {
      cb(new Error('Invalid file type'));
    }
  },
  limits: {
    fileSize: 10 * 1024 * 1024 // 10MB
  }
});

Database Backups

# Backup script
#!/bin/bash
mysqldump -u user -p database > backup_$(date +%Y%m%d_%H%M%S).sql
# Rotate old backups
find . -name "backup_*.sql" -mtime +30 -delete

Monitoring

Set up monitoring for security events:

// Audit logging
import auditlog from 'express-auditlog';

app.use(auditlog());

Security Checklist

  • Session cookies are HTTP-only
  • Passwords are hashed with bcrypt
  • SQL queries use parameterized statements
  • CORS is properly configured
  • Rate limiting is enabled
  • Security headers are set
  • Environment variables are not committed
  • Input is validated on all endpoints
  • File uploads are sanitized
  • Dependencies are up to date

Common Vulnerabilities

SQL Injection Prevention

// ✅ SAFE
User.findOne({ where: { email: email } });

// ❌ UNSAFE
User.findAll({ where: `email = '${email}'` });

XSS Prevention

// ✅ SAFE - Sanitize user input
const displayName = sanitize(userInput);

// ❌ UNSAFE - Direct output
res.send(userInput);

Dependencies

Keep dependencies updated:

# Check for updates
npm outdated

# Update all
npm update

# Update specific package
npm update express

Security Audits

# Run security audit
npm audit

# Fix vulnerabilities
npm audit fix

Best Practices

  1. Principle of Least Privilege: Use minimal permissions
  2. Defense in Depth: Multiple security layers
  3. Fail Securely: Default to safe responses
  4. Log Security Events: Monitor for suspicious activity
  5. Regular Audits: Periodic security reviews