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>
This commit is contained in:
287
docs/security.md
Normal file
287
docs/security.md
Normal file
@@ -0,0 +1,287 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user