A new README for the combined project with setup from the Flashpoint Ultimate torrent. New feature docs for streaming, proxy sign-in and game saves; deployment, upgrade and streaming READMEs updated for the single compose file; the project renamed throughout.
28 KiB
Security Measures Reference
Overview
This document catalogs all security measures implemented in the Dotcom Arcade application, addressing OWASP Top 10 vulnerabilities and security best practices.
Authentication & Authorization
JWT-Based Authentication
Implementation: backend/src/middleware/auth.ts
- ✅ JWT tokens for stateless authentication
- ✅ Access tokens (1 hour expiry)
- ✅ Refresh tokens (7 days expiry)
- ✅ Token rotation on refresh
- ✅ Automatic token cleanup on logout
Security Features:
- Secret key from environment variable
- Token signature verification
- Expiration validation
- User existence validation on each request
Role-Based Access Control (RBAC)
Implementation: backend/src/middleware/rbac.ts
- ✅ Permission-based authorization
- ✅ Role hierarchy (admin, moderator, user, guest)
- ✅ Granular permissions (e.g.,
games.play,users.manage) - ✅ Permission caching for performance
Permission System:
requirePermission('settings.update');
requirePermission('users.read');
requirePermission('games.play');
Password Security
Implementation: backend/src/services/AuthService.ts
- ✅ bcrypt hashing (10 rounds)
- ✅ Minimum password length (6 characters)
- ✅ Password validation with Zod schemas
- ✅ No password storage in plaintext
Guest Access Validation
Status: ✅ Hardened (2026-02-07)
Implementation: backend/src/middleware/auth.ts and
frontend/src/store/auth.ts
- ✅ Guest users (id=0) excluded from shared access bypass in
validateSharedGameAccess - ✅ Guest permissions restricted to read-only:
games.read,playlists.readonly - ✅
games.playremoved from guest role (requires authentication)
Data Integrity (Transaction Safety)
Status: ✅ Hardened (2026-02-07)
Implementation: Multiple backend services
All operations with TOCTOU (Time-of-Check-Time-of-Use) race conditions now wrapped in database transactions:
AuthService.register()— uniqueness checks + INSERT in single transactionUserService.createUser()— uniqueness checks + INSERT in single transactionSystemSettingsService.updateCategory()— multi-setting updates in single transactionUserPlaylistService.cloneSharedPlaylist()— playlist creation + game inserts in single transaction
Protection:
- ✅ Prevents duplicate user registration under concurrent requests
- ✅ Ensures atomic multi-row updates (all-or-nothing)
- ✅ Uses
better-sqlite3synchronous transaction API for correctness
Rate Limiting
Login Endpoint Protection
Implementation: backend/src/routes/auth.ts:17-38
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // 5 attempts per IP
skipSuccessfulRequests: false,
});
Protection Against:
- Brute force password attacks
- Credential stuffing
- Account enumeration
Registration Endpoint Protection
Implementation: backend/src/routes/auth.ts:41-61
const registerLimiter = rateLimit({
windowMs: 60 * 60 * 1000, // 1 hour
max: 3, // 3 registrations per IP per hour
});
Protection Against:
- Account creation spam
- Resource exhaustion
- Abuse of registration system
Input Validation
Zod Schema Validation
Implementation: Throughout route files
Examples:
// Login validation
const loginSchema = z.object({
username: z.string().min(3).max(50),
password: z.string().min(6),
});
// Registration validation
const registerSchema = z.object({
username: z.string().min(3).max(50),
email: z.string().email(),
password: z.string().min(6),
});
Validation Middleware: backend/src/middleware/validation.ts
- ✅ Centralized validation error handling
- ✅ Type-safe request validation
- ✅ Automatic 400 Bad Request responses
Extended Zod Validation (2026-02-07)
Status: ✅ Expanded (2026-02-07)
Additional routes now use Zod validation for request bodies and query parameters:
- community-playlists.ts:
downloadUrlvalidated withz.string().url().max(2000)to prevent extremely long URLs - shared-playlists.ts:
newTitlevalidated withz.string().min(1).max(255).optional()for title updates - cache.ts:
cacheTypevalidated withz.enum(['gameSearch', 'permissions', 'all']).optional()to restrict cache invalidation targets - activities.ts:
timeRangevalidated against allowlist['24h', '7d', '30d']to prevent invalid time range parameters
Protection:
- ✅ Prevents malformed request bodies from reaching service layer
- ✅ Restricts query/body values to explicit allowlists
- ✅ Returns 400 Bad Request with validation errors
- ✅ Consistent validation patterns across all endpoints
Route ID Parameter Validation
Status: ✅ Hardened (2026-02-06)
Implementation: All route files handling /:id parameters
Route ID parameters are now validated with isNaN() guard after parseInt():
// users.ts, roles.ts, etc.
const id = parseInt(req.params.id, 10);
if (isNaN(id)) {
return res.status(400).json({ error: 'Invalid ID' });
}
// Safe to use in queries
const user = UserDatabaseService.get('SELECT * FROM users WHERE id = ?', [id]);
Protection:
- ✅ Prevents NaN parameters from reaching SQL queries
- ✅ Returns 400 Bad Request instead of misleading 404
- ✅ Provides clear error feedback to API consumers
Query Parameter Limits
Status: ✅ Hardened (2026-02-06)
Implementation: Query parameters capped to prevent excessive queries
Affected Endpoints:
users.ts:limitcapped at 100favorites.ts:limitcapped at 100games.ts:/randomlibrary param validated with Zod enumplaylists.ts: Request bodies validated with Zod schemasauth.ts: Zod validation errors aggregate all messages
Pattern:
const limit = Math.min(req.query.limit ? parseInt(req.query.limit as string) : 10, 100);
const games = GameService.getGames(limit);
Protection:
- ✅ Prevents clients from requesting
limit=999999and dumping entire tables - ✅ Reduces database load and query execution time
- ✅ Protects against unintentional DoS attacks
Zod Error Aggregation
Status: ✅ Hardened (2026-02-06)
Implementation: backend/src/routes/auth.ts
Authentication endpoints now aggregate all Zod validation errors instead of showing only the first:
// BEFORE: Only first error shown
res.status(400).json({ error: 'Username is required' });
// AFTER: All validation errors aggregated
res.status(400).json({
error: 'Validation failed',
details: [
'Username is required',
'Password must be at least 6 characters',
'Email is invalid'
]
});
Protection:
- ✅ Provides complete feedback for form validation
- ✅ Helps API consumers fix all issues at once
- ✅ Reduces round-trip API calls
Path Traversal Prevention
Status: ✅ Comprehensive Protection Implemented (2026-01-29)
Centralized Security Utilities: backend/src/game/utils/pathSecurity.ts
The backend game module has comprehensive directory traversal protection through centralized validation utilities:
1. Path Sanitization & Validation:
sanitizeAndValidatePath(basePath: string, requestPath: string): string
- Normalizes paths and ensures they stay within allowed directories
- Platform-aware (Windows/Unix)
- Blocks:
../, absolute paths, null bytes, URL-encoded traversal
2. URL Path Sanitization:
sanitizeUrlPath(urlPath: string): string
- Detects null bytes (
\0) - Blocks URL-encoded directory traversal (
..%2F,..%5C) - Blocks backslash path traversal (
..\\)
Implementation Coverage:
Legacy Server (backend/src/game/legacy-server.ts):
- ✅ URL path sanitization at entry point
- ✅ Path validation before all file access
- ✅ Separate validation for htdocs and CGI paths
GameZip Server (backend/src/game/gamezipserver.ts):
- ✅ URL path sanitization in file requests
- ✅ Mount ID validation (prevents path separators)
- ✅ ZIP path validation (ensures files within games directory)
Test Coverage: 17 tests (all passing) in backend/src/game/utils/pathSecurity.test.ts
For detailed information: See
docs/13-security/directory-traversal-protection.md (now covers backend game
module)
DoS & Resource Exhaustion Protection
Request Body Size Limits
Backend Game Module: backend/src/game/gamezipserver.ts
private readBody(req: http.IncomingMessage, maxSize: number = 1024 * 1024): Promise<string> {
return new Promise((resolve, reject) => {
let body = '';
req.on('data', chunk => {
body += chunk.toString();
if (body.length > maxSize) {
req.destroy();
reject(new Error('Request body too large'));
}
});
});
}
Protection: 1MB maximum request size prevents memory exhaustion attacks
ZIP Manager Memory Protection
Implementation: backend/src/game/zip-manager.ts
private mountedZips = new LRUCache<string, MountedZip>({
max: 100, // Maximum 100 ZIPs mounted simultaneously
ttl: 30 * 60 * 1000, // 30-minute TTL
dispose: async (value, key) => {
await value.zip.close(); // Auto-cleanup on eviction
}
});
Protection:
- Prevents unlimited ZIP mounts
- Automatic cleanup of old mounts
- Resource leak prevention
ZIP Entry Size Limits
Status: ✅ Implemented (2026-02-06)
Implementation: backend/src/game/zip-manager.ts
const MAX_ZIP_ENTRY_SIZE = 50 * 1024 * 1024; // 50MB per entry
Protection:
- Prevents memory exhaustion from oversized ZIP entries
- Blocks decompression bombs
- Enforced during ZIP mounting
Rate Limiting on Resource-Intensive Endpoints
Status: ✅ Implemented (2026-02-06)
Error Reporting (backend/src/routes/errors.ts):
router.post('/', rateLimitStrict, ...) // Strict rate limiting
Updates (backend/src/routes/updates.ts):
router.get('/', rateLimitStandard, ...) // Standard rate limiting
Protection:
- Prevents abuse of error submission endpoint
- Prevents excessive update check requests
- Reduces server resource consumption
Logging & Monitoring
Activity Logging
Implementation: backend/src/middleware/activityLogger.ts
Tracked Actions:
- User login/logout
- Resource creation/updates/deletions
- Permission changes
- Failed authentication attempts
Logged Information:
- User ID
- Action type
- Resource affected
- IP address
- Timestamp
Sensitive Data Sanitization
Implementation: backend/src/middleware/errorHandler.ts:18-42
function sanitizeBody(body: any): any {
const sanitized = { ...body };
const sensitiveFields = [
'password',
'currentPassword',
'newPassword',
'token',
'refreshToken',
'secret',
'apiKey',
'accessToken',
];
sensitiveFields.forEach((field) => {
if (sanitized[field]) {
sanitized[field] = '[REDACTED]';
}
});
return sanitized;
}
Protection: Prevents passwords and tokens from appearing in logs
CORS Configuration
Backend Service (Restrictive)
Implementation: backend/src/server.ts
cors({
origin: process.env.DOMAIN || 'http://localhost:5173',
credentials: true,
});
Security:
- ✅ Whitelist specific origin only
- ✅ Credentials allowed for authenticated requests
- ✅ Protects sensitive API endpoints
Backend Game Content Routes (Permissive - Justified)
Implementation: backend/src/game/ routes (/game-proxy/*, /game-zip/*)
res.setHeader('Access-Control-Allow-Origin', '*');
Justification: See docs/12-reference/cors-security-decision.md
- Serves public, read-only game content
- No sensitive data exposure
- Supports game embedding use cases
- Integrated into the same backend service as restrictive API routes
SQL Injection Prevention
Parameterized Queries
Implementation: Throughout backend/src/services/
Example from GameService.ts:
const game = DatabaseService.get('SELECT * FROM game WHERE id = ?', [id]);
Protection:
- ✅ All database queries use parameterized statements
- ✅ No string concatenation in SQL queries
- ✅ Better-sqlite3 automatic escaping
SSRF (Server-Side Request Forgery) Prevention
DNS Rebinding Prevention
Status: ✅ Implemented (2026-02-07)
Implementation: backend/src/game/services/GameDataDownloader.ts
Pre-checks DNS resolution for non-IP hostnames before connecting, blocking private/internal addresses:
// Resolve hostname and check for private IPs
const addr = await dns.lookup(hostname);
if (isPrivateAddress(addr.address)) {
throw new Error('Download blocked: resolves to private address');
}
Protection:
- ✅ Prevents DNS rebinding attacks where public hostnames resolve to internal IPs
- ✅ Blocks downloads from 127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16
- ✅ Check performed before connection, not after
Community Playlists Domain Allowlist
Status: ✅ Implemented (2026-02-06)
Implementation: backend/src/routes/community-playlists.ts
const ALLOWED_DOWNLOAD_DOMAINS = [
'flashpointarchive.org',
'*.flashpointarchive.org',
];
// Domain validation before request
if (!validateDomain(url, ALLOWED_DOWNLOAD_DOMAINS)) {
return res.status(400).json({ error: 'Invalid domain' });
}
Security Features:
- ✅ Whitelist of allowed domains for playlist downloads
- ✅ HTTPS enforcement for downloads
- ✅ Prevents internal network access (e.g., 127.0.0.1, internal IPs)
Consolidated Constants (2026-02-07):
The domain allowlist is now a single exported constant from
CommunityPlaylistService.ts, eliminating duplication between the route and
service files. This creates a single source of truth for allowed domains across
the application.
// CommunityPlaylistService.ts
export const ALLOWED_DOWNLOAD_DOMAINS = [
'flashpointarchive.org',
'*.flashpointarchive.org',
];
This pattern prevents inconsistencies where the route validation might differ from the service-level validation.
Game Data Download Redirect Limits
Status: ✅ Implemented (2026-02-06)
Implementation: backend/src/game/services/GameDataDownloader.ts
private maxRedirects = 5; // Maximum redirect chain length
Redirect Handling:
// Drain response body on redirect/error to prevent connection leaks
response.resume();
Protection:
- Limits redirect chains to 5 hops (prevents infinite redirect loops)
- Drains response bodies to prevent resource leaks
- Prevents redirect-based SSRF attacks
Error Handling & Information Disclosure Prevention
Centralized Error Handler
Implementation: backend/src/middleware/errorHandler.ts
Features:
- ✅ Distinguishes operational vs programmer errors
- ✅ Generic error messages to clients (prevents information leakage)
- ✅ Detailed logging for debugging
- ✅ Sanitized request bodies in logs
Example:
// Unhandled errors
logger.error('[UnhandledError]', {
message: err.message,
stack: err.stack,
body: sanitizeBody(req.body), // Sensitive data redacted
});
// Client receives generic message
res.status(500).json({
error: { message: 'Internal server error' },
});
Async Error Handler Wrapper
Status: ✅ Hardened (2026-02-06)
Implementation: backend/src/routes/system-settings.ts and other route files
All async route handlers are now wrapped with asyncHandler to prevent unhandled promise rejections:
// BEFORE: Uncaught rejections if error thrown in async handler
router.get('/', async (req, res) => {
const result = await someService.getResult(); // If rejects, crashes server
res.json(result);
});
// AFTER: Error caught and passed to error handler middleware
router.get(
'/',
asyncHandler(async (req, res) => {
const result = await someService.getResult();
res.json(result);
})
);
Implementation:
function asyncHandler(fn: Function) {
return (req: Request, res: Response, next: NextFunction) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
}
Protection:
- ✅ Prevents unhandled promise rejections from crashing server
- ✅ Errors reach centralized error handler
- ✅ Consistent error responses to clients
Middleware Coverage (2026-02-07):
asyncHandler now also wraps middleware functions:
authenticatemiddleware inbackend/src/middleware/auth.tsoptionalAuthmiddleware inbackend/src/middleware/auth.ts- All game content route handlers:
game-zip.ts,game-proxy.ts,github.ts
This ensures middleware errors are also caught and handled properly instead of propagating as unhandled rejections.
Sensitive Log Sanitization
Status: ✅ Hardened (2026-02-06)
Implementation: backend/src/routes/auth.ts
Username input is now sanitized before logging to prevent leaking sensitive data:
private sanitizeUsername(username: unknown): string {
if (typeof username !== 'string') {
return 'unknown';
}
// Truncate long usernames to prevent log injection
return username.substring(0, 50);
}
// In login handler
logger.warn(`[Auth] Login attempt failed for user: ${this.sanitizeUsername(req.body.username)}`);
Protection:
- ✅ Prevents username PII exposure in logs
- ✅ Truncates long inputs to prevent log injection
- ✅ Safe fallback for non-string values
Download Endpoint Error Messages & SSE Handling
Status: ✅ Hardened (2026-02-06)
Implementation: backend/src/routes/downloads.ts
Error Response Sanitization:
Error responses no longer leak internal implementation details to clients:
// ✗ Before: Error: File not found at /D/Flashpoint/Data/Games/abc123.zip
// ✓ After: { error: 'File not found' }
Server-Sent Events (SSE) Error Handling:
SSE endpoint now properly checks res.headersSent before attempting to send error responses:
// BEFORE: Errors crash if headers already sent
try {
// SSE streaming operations
} catch (error) {
res.status(500).json({ error: 'Server error' }); // Crashes if headers sent!
}
// AFTER: Check headers before responding
try {
// SSE streaming operations
} catch (error) {
if (res.headersSent) {
// Headers already sent, write SSE error event
res.write(`data: {"error": "Server error"}\n\n`);
res.end();
} else {
// Headers not sent, use normal response
res.status(500).json({ error: 'Server error' });
}
}
Protection:
- ✅ Prevents attackers from discovering system paths
- ✅ Prevents crashes when headers already sent
- ✅ Graceful error handling for streaming responses
Health Endpoint Information Disclosure
Status: ✅ Hardened (2026-02-06)
Implementation: backend/src/server.ts
Health endpoint (/health) no longer exposes sensitive information:
// ✗ Before: { status: 'ok', flashpointPath: '/D/Flashpoint' }
// ✓ After: { status: 'ok' }
Protection: Prevents disclosure of system paths to unauthorized users
Additional Code Quality Improvements
Status: ✅ Implemented (2026-02-07)
Low-Priority Security Enhancements:
- headersSent guard in proxy-request-handler: The
sendError()method inproxy-request-handler.tsnow checksres.headersSentbefore attempting to send error responses, preventing "Cannot set headers after they are sent" errors in streaming scenarios. - Private visibility on internal methods:
findActiveDownload()ingamezipserver.tsis nowprivate- it was only used internally but was publicly accessible.PRIVATE_IP_RANGESinGameDataDownloader.tsis nowstatic readonly. - Removed unnecessary exports: Internal-only constants like
CUSTOM_MIME_TYPESno longer exported, reducing the public API surface. Convenience re-exports removed from service index files.
XSS Prevention
Backend Game Module HTML Escaping
Status: ✅ Implemented (2026-02-06)
Implementation: backend/src/game/gamezipserver.ts
The GameZip server dynamically generates HTML loading pages with user-controlled
data (progress values, source names, elapsed time). All interpolated values are
now escaped using a centralized escapeHtml() utility:
private escapeHtml(text: string): string {
const map: Record<string, string> = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": ''',
};
return text.replace(/[&<>"']/g, (char) => map[char]);
}
Protected Values:
- Progress percentages and values
- Download source names
- Elapsed time and status messages
Prevention: Blocks XSS attacks through HTML injection in dynamically generated loading pages
Frontend Security
React Built-in Protection:
- ✅ Automatic escaping of JSX expressions
- ✅ No
dangerouslySetInnerHTMLusage - ✅ Content Security Policy headers (if configured)
Token Storage
Current Implementation: frontend/src/store/auth.ts and backend/src/middleware/auth.ts
Refresh Tokens: HTTP-only cookies
- ✅ Protected from XSS attacks (JavaScript cannot access)
- ✅ Cookie name:
fp_refresh - ✅ Cookie path:
/api/auth(scoped to auth routes only) - ✅ Expiry: 30 days
- ✅ Secure flag: enabled in production (HTTPS only)
- ✅ SameSite:
lax(provides CSRF protection) - ✅ Automatically sent with requests via
withCredentials: trueon axios client
Access Tokens: Memory-only (Zustand store)
- ✅ Never persisted to localStorage
- ✅ Lost on page reload (recovered via
/api/auth/refreshcookie) - ✅ Prevents XSS persistence attacks
Session Recovery on Page Reload:
- Frontend initializes auth store (access token is empty)
- Frontend makes
GET /api/auth/refresh(cookie sent automatically) - Backend validates refresh token from cookie
- Backend returns new access token (no cookie refresh needed)
- Frontend stores access token in memory
Type Safety
TypeScript Strict Mode:
- ✅ No implicit
anytypes - ✅ Null/undefined checks
- ✅ Type-safe API calls
Authentication on Protected Endpoints
Game Management Endpoints
Status: ✅ Implemented (2026-02-06)
Protected Endpoints (backend/src/routes/game-zip.ts):
- POST /game-zip/mount - Mount a ZIP file (requires
settings.update) - POST /game-zip/unmount - Unmount a ZIP file (requires
settings.update) - GET /game-zip/list - List mounted ZIPs (requires
settings.update)
Authentication Middleware:
router.post('/mount', authenticate, requirePermission('settings.update'), ...);
router.post('/unmount', authenticate, requirePermission('settings.update'), ...);
router.get('/list', authenticate, requirePermission('settings.update'), ...);
Exception: File-serving route (GET /*) remains unauthenticated
- Reason: Game content must load in iframes without authentication
- Security: No sensitive data exposed (public game files only)
CGI & Legacy Content Security
CGI Symlink Bypass Prevention
Status: ✅ Implemented (2026-02-07)
Implementation: backend/src/game/cgi/cgi-executor.ts
CGI script path validation now resolves symlinks before checking path boundaries:
// Resolve symlinks before validation
const realPath = await fs.realpath(scriptPath);
const validPath = sanitizeAndValidatePath(cgiBaseDir, realPath);
Protection:
- ✅ Prevents symlink-based path traversal (symlink pointing outside allowed directory)
- ✅ Uses
fs.realpath()to resolve all symbolic links before validation - ✅ Validates the resolved path stays within allowed CGI directory
CGI Environment Variable Allowlist
Status: ✅ Implemented (2026-02-06)
Implementation: backend/src/game/cgi/cgi-executor.ts
CGI scripts no longer receive unrestricted environment variables. Using an allowlist instead of a denylist:
private static readonly ALLOWED_ENV_VARS = new Set([
'REQUEST_METHOD',
'REQUEST_URI',
'QUERY_STRING',
'CONTENT_TYPE',
'CONTENT_LENGTH',
'HTTP_HOST',
'HTTP_USER_AGENT',
'REMOTE_ADDR',
'PATH_INFO',
'PATH_TRANSLATED',
// ... more safe variables
]);
Protection:
- ✅ CGI scripts only receive necessary variables
- ✅ Prevents leakage of sensitive env vars (API keys, paths, tokens)
- ✅ Blocks information disclosure attacks
HTTP Header Filtering for CGI
Status: ✅ Implemented (2026-02-06)
Implementation: backend/src/game/cgi/cgi-executor.ts
private static readonly ALLOWED_CGI_HEADERS = new Set([
'accept',
'accept-encoding',
'accept-language',
'content-type',
'content-length',
'user-agent',
'referer',
// ... more safe headers
]);
Protection:
- ✅ Filters HTTP headers passed to CGI scripts
- ✅ Prevents header injection attacks
- ✅ Blocks sensitive header propagation
Database Security
Flashpoint Database
Access Level: READ-ONLY
- ✅ No write operations permitted
- ✅ Separate from user database
- ✅ File watching for external updates
User Database
Security Features:
- ✅ SQLite with file-level permissions
- ✅ Schema migrations tracked
- ✅ Transaction support for data integrity
- ✅ Foreign key constraints enabled
Graceful Shutdown
Implementation: backend/src/server.ts:177-200
Cleanup on SIGTERM/SIGINT:
- ✅ Stop scheduled jobs
- ✅ Stop permission cache cleanup
- ✅ Stop play session cleanup
- ✅ Close HTTP server gracefully
- ✅ Force exit after timeout
Protection: Prevents resource leaks and incomplete operations
Security Checklist
✅ Implemented
- Authentication (JWT)
- Authorization (RBAC)
- Password hashing (bcrypt)
- Rate limiting (login, registration, errors, updates)
- Input validation (Zod schemas, route ID parameters, query limits)
- SQL injection prevention (parameterized queries)
- Path traversal prevention (comprehensive, with 17 test cases)
- DoS protection (request size limits, LRU cache, ZIP entry limits)
- Sensitive data sanitization in logs (username truncation)
- Activity logging
- Error handling (centralized, no information leakage, async handlers)
- CORS configuration (restrictive for backend, dynamic)
- Graceful shutdown
- XSS prevention (HTML escaping in generated templates)
- SSRF prevention (domain allowlist, redirect limits)
- Authentication on game management endpoints
- CGI environment variable allowlist
- HTTP header filtering for CGI
- Health endpoint hardening (no path disclosure)
- SSE error handling (headers check before responding)
- DNS rebinding prevention (private IP check before download)
- Symlink bypass prevention (realpath before path validation)
- Guest access hardening (read-only permissions, shared access check)
- TOCTOU race condition prevention (database transactions)
- SSE write-after-close prevention (closed flag guard)
- AbortSignal listener cleanup (memory leak prevention)
- Downloaded file path re-validation (post-download directory check)
- Extended Zod validation on request bodies (community playlists, shared playlists, cache, activities)
- asyncHandler on all async middleware (authenticate, optionalAuth)
- Consolidated domain allowlists (single source of truth)
⚠️ Recommended Enhancements
- Content Security Policy headers
- Database indexes (requires Flashpoint Archive coordination)
- Security headers (Helmet middleware)
- HTTPS in production
- Rate limiting on additional endpoints (CDN fallback, external requests)
References
Last Updated: 2026-02-07 Review Status: Comprehensive (All critical, high, and medium-severity fixes implemented: HTTP-only cookies, DNS rebinding, symlink bypass, TOCTOU transactions, guest hardening, resource cleanup, SSE safety, downloaded file validation, extended Zod validation, asyncHandler middleware, consolidated domain allowlists)