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

288 lines
5.6 KiB
Markdown

# 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
```javascript
// 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
```typescript
// 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:
```typescript
// ✅ 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:
```typescript
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
```typescript
// 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
```typescript
// 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
```typescript
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:
```bash
# .env file
SESSION_SECRET=your-secure-random-string
X_GM_SECRET=your-gm-secret-key
DATABASE_PASSWORD=your-database-password
```
Never commit `.env` files:
```bash
# .gitignore
.env
.env.*
!/.env.example
```
## API Security
### GM Authentication
GM endpoints require special authentication:
```typescript
// 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
```typescript
const apiLimiter = rateLimit({
windowMs: 60 * 1000, // 1 minute
max: 100,
keyGenerator: (req) => req.ip
});
app.use('/api/', apiLimiter);
```
## File Upload Security
```typescript
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
```bash
# 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:
```typescript
// 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
```typescript
// ✅ SAFE
User.findOne({ where: { email: email } });
// ❌ UNSAFE
User.findAll({ where: `email = '${email}'` });
```
### XSS Prevention
```typescript
// ✅ SAFE - Sanitize user input
const displayName = sanitize(userInput);
// ❌ UNSAFE - Direct output
res.send(userInput);
```
## Dependencies
Keep dependencies updated:
```bash
# Check for updates
npm outdated
# Update all
npm update
# Update specific package
npm update express
```
## Security Audits
```bash
# 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