security: implement CRITICAL and MEDIUM security fixes with environment profiles
This commit addresses all CRITICAL and MEDIUM security vulnerabilities
identified in the security audit with environment-aware configuration.
## Docker Compose Profiles
- Added docker-compose.dev.yml for development (relaxed security)
- Added docker-compose.prod.yml for production (strict security)
- Environment-specific configurations for rate limiting, CSRF, logging
## CRITICAL Fixes (P0)
1. Fixed insecure random number generation
- Replaced Math.random() with crypto.randomBytes() for verification codes
- Now cryptographically secure
2. Implemented rate limiting
- express-rate-limit for all endpoints
- Strict limits on auth endpoints (5 attempts in dev=off, prod=5)
- Email endpoint limits (20 in dev, 3 in prod)
- API-wide rate limiting
3. Added request body size limits
- Development: 50MB (for testing)
- Production: 10KB (security)
4. Fixed user enumeration vulnerability
- Generic error message for registration
- No disclosure of which field exists
5. Added security headers
- helmet.js with CSP, HSTS, XSS protection
- No-sniff, hide powered-by headers
## MEDIUM Fixes (P1)
6. Strengthened password policy
- Environment-aware validation (8+ chars)
- Production: requires uppercase, lowercase, number
- Development: relaxed for testing
7. Enhanced input validation
- Validation for all auth endpoints
- WSDC ID validation (numeric, max 10 digits)
- Name validation (safe characters only)
- Email normalization
8. Added input sanitization
- DOMPurify for XSS prevention
- Sanitize all user inputs in emails
- Timing-safe string comparison for tokens
9. Improved error handling
- Generic errors in production
- Detailed errors only in development
- Proper error logging
10. Enhanced CORS configuration
- Whitelist-based origin validation
- Environment-specific allowed origins
- Credentials support
## New Files
- backend/src/config/security.js - Environment-aware security config
- backend/src/middleware/rateLimiter.js - Rate limiting middleware
- backend/src/utils/sanitize.js - Input sanitization utilities
- backend/.env.example - Development environment template
- backend/.env.production.example - Production environment template
- docker-compose.dev.yml - Development overrides
- docker-compose.prod.yml - Production configuration
- docs/DEPLOYMENT.md - Complete deployment guide
- docs/SECURITY_AUDIT.md - Full security audit report
- .gitignore - Updated to exclude .env files
## Dependencies Added
- helmet (^8.1.0) - Security headers
- express-rate-limit (^8.2.1) - Rate limiting
- dompurify (^3.3.0) - XSS prevention
- jsdom (^27.2.0) - DOM manipulation for sanitization
## Testing
- ✅ Password validation works (weak passwords rejected)
- ✅ User enumeration fixed (generic error messages)
- ✅ WSDC lookup functional
- ✅ Registration flow working
- ✅ Rate limiting active (environment-aware)
- ✅ Security headers present
## Usage
Development:
docker compose -f docker-compose.yml -f docker-compose.dev.yml up
Production:
docker compose -f docker-compose.yml -f docker-compose.prod.yml up
See docs/DEPLOYMENT.md for detailed instructions.
This commit is contained in:
@@ -34,9 +34,13 @@ function generateVerificationToken() {
|
||||
return crypto.randomBytes(32).toString('hex');
|
||||
}
|
||||
|
||||
// Generate 6-digit verification code
|
||||
// Generate 6-digit verification code (cryptographically secure)
|
||||
function generateVerificationCode() {
|
||||
return Math.floor(100000 + Math.random() * 900000).toString();
|
||||
// Use crypto.randomBytes for cryptographically secure random numbers
|
||||
const bytes = crypto.randomBytes(4);
|
||||
const num = bytes.readUInt32BE(0);
|
||||
// Ensure 6 digits (100000 to 999999)
|
||||
return String(num % 900000 + 100000);
|
||||
}
|
||||
|
||||
// Calculate token expiry time
|
||||
|
||||
80
backend/src/utils/sanitize.js
Normal file
80
backend/src/utils/sanitize.js
Normal file
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Input Sanitization Utilities
|
||||
* Prevents XSS and injection attacks
|
||||
*/
|
||||
|
||||
const createDOMPurify = require('dompurify');
|
||||
const { JSDOM } = require('jsdom');
|
||||
|
||||
const window = new JSDOM('').window;
|
||||
const DOMPurify = createDOMPurify(window);
|
||||
|
||||
/**
|
||||
* Sanitize HTML input to prevent XSS
|
||||
* @param {string} dirty - Untrusted HTML string
|
||||
* @returns {string} - Sanitized string
|
||||
*/
|
||||
function sanitizeHtml(dirty) {
|
||||
if (typeof dirty !== 'string') return '';
|
||||
|
||||
return DOMPurify.sanitize(dirty, {
|
||||
ALLOWED_TAGS: [], // Strip all HTML tags
|
||||
ALLOWED_ATTR: [],
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize text for use in emails
|
||||
* @param {string} text - User input text
|
||||
* @returns {string} - Sanitized text
|
||||
*/
|
||||
function sanitizeForEmail(text) {
|
||||
if (typeof text !== 'string') return '';
|
||||
|
||||
// Remove HTML tags and encode special characters
|
||||
return DOMPurify.sanitize(text, {
|
||||
ALLOWED_TAGS: [],
|
||||
ALLOWED_ATTR: [],
|
||||
}).trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize username (alphanumeric + underscore only)
|
||||
* @param {string} username - Username input
|
||||
* @returns {string} - Sanitized username
|
||||
*/
|
||||
function sanitizeUsername(username) {
|
||||
if (typeof username !== 'string') return '';
|
||||
|
||||
return username.replace(/[^a-zA-Z0-9_]/g, '').trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Timing-safe string comparison
|
||||
* Prevents timing attacks on token comparison
|
||||
* @param {string} a - First string
|
||||
* @param {string} b - Second string
|
||||
* @returns {boolean} - True if strings match
|
||||
*/
|
||||
function timingSafeEqual(a, b) {
|
||||
const crypto = require('crypto');
|
||||
|
||||
if (typeof a !== 'string' || typeof b !== 'string') return false;
|
||||
if (a.length !== b.length) return false;
|
||||
|
||||
try {
|
||||
return crypto.timingSafeEqual(
|
||||
Buffer.from(a, 'utf8'),
|
||||
Buffer.from(b, 'utf8')
|
||||
);
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
sanitizeHtml,
|
||||
sanitizeForEmail,
|
||||
sanitizeUsername,
|
||||
timingSafeEqual,
|
||||
};
|
||||
Reference in New Issue
Block a user