fix(security): add localhost-only protection for admin endpoints
Adds middleware to restrict /api/admin/restart and /api/admin/shutdown to localhost-only access. This prevents DoS attacks when the worker service is bound to 0.0.0.0 for remote UI access. Implementation: - Created requireLocalhost middleware in middleware.ts - Applied to both admin endpoints - Checks client IP against localhost addresses (127.0.0.1, ::1, etc.) - Returns 403 Forbidden for non-localhost requests Addresses security concern raised in PR #368 with cleaner DRY approach. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -32,7 +32,7 @@ import { TimelineService } from './worker/TimelineService.js';
|
|||||||
import { SessionEventBroadcaster } from './worker/events/SessionEventBroadcaster.js';
|
import { SessionEventBroadcaster } from './worker/events/SessionEventBroadcaster.js';
|
||||||
|
|
||||||
// Import HTTP layer
|
// Import HTTP layer
|
||||||
import { createMiddleware, summarizeRequestBody as summarizeBody } from './worker/http/middleware.js';
|
import { createMiddleware, summarizeRequestBody as summarizeBody, requireLocalhost } from './worker/http/middleware.js';
|
||||||
import { ViewerRoutes } from './worker/http/routes/ViewerRoutes.js';
|
import { ViewerRoutes } from './worker/http/routes/ViewerRoutes.js';
|
||||||
import { SessionRoutes } from './worker/http/routes/SessionRoutes.js';
|
import { SessionRoutes } from './worker/http/routes/SessionRoutes.js';
|
||||||
import { DataRoutes } from './worker/http/routes/DataRoutes.js';
|
import { DataRoutes } from './worker/http/routes/DataRoutes.js';
|
||||||
@@ -208,8 +208,8 @@ export class WorkerService {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Admin endpoints for process management
|
// Admin endpoints for process management (localhost-only)
|
||||||
this.app.post('/api/admin/restart', async (_req, res) => {
|
this.app.post('/api/admin/restart', requireLocalhost, async (_req, res) => {
|
||||||
res.json({ status: 'restarting' });
|
res.json({ status: 'restarting' });
|
||||||
|
|
||||||
// On Windows, if managed by wrapper, send message to parent to handle restart
|
// On Windows, if managed by wrapper, send message to parent to handle restart
|
||||||
@@ -230,7 +230,7 @@ export class WorkerService {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.app.post('/api/admin/shutdown', async (_req, res) => {
|
this.app.post('/api/admin/shutdown', requireLocalhost, async (_req, res) => {
|
||||||
res.json({ status: 'shutting_down' });
|
res.json({ status: 'shutting_down' });
|
||||||
|
|
||||||
// On Windows, if managed by wrapper, send message to parent to handle shutdown
|
// On Windows, if managed by wrapper, send message to parent to handle shutdown
|
||||||
|
|||||||
@@ -60,6 +60,34 @@ export function createMiddleware(
|
|||||||
return middlewares;
|
return middlewares;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Middleware to require localhost-only access
|
||||||
|
* Used for admin endpoints that should not be exposed when binding to 0.0.0.0
|
||||||
|
*/
|
||||||
|
export function requireLocalhost(req: Request, res: Response, next: NextFunction): void {
|
||||||
|
const clientIp = req.ip || req.connection.remoteAddress || '';
|
||||||
|
const isLocalhost =
|
||||||
|
clientIp === '127.0.0.1' ||
|
||||||
|
clientIp === '::1' ||
|
||||||
|
clientIp === '::ffff:127.0.0.1' ||
|
||||||
|
clientIp === 'localhost';
|
||||||
|
|
||||||
|
if (!isLocalhost) {
|
||||||
|
logger.warn('SECURITY', 'Admin endpoint access denied - not localhost', {
|
||||||
|
endpoint: req.path,
|
||||||
|
clientIp,
|
||||||
|
method: req.method
|
||||||
|
});
|
||||||
|
res.status(403).json({
|
||||||
|
error: 'Forbidden',
|
||||||
|
message: 'Admin endpoints are only accessible from localhost'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Summarize request body for logging
|
* Summarize request body for logging
|
||||||
* Used to avoid logging sensitive data or large payloads
|
* Used to avoid logging sensitive data or large payloads
|
||||||
|
|||||||
Reference in New Issue
Block a user