diff --git a/Overlord-Server/src/auth.ts b/Overlord-Server/src/auth.ts index 69f30e7..53e7289 100644 --- a/Overlord-Server/src/auth.ts +++ b/Overlord-Server/src/auth.ts @@ -20,7 +20,7 @@ const tokenCache = new Map< string, { payload: JWTPayload; timestamp: number } >(); -const TOKEN_CACHE_TTL = 5000; +const TOKEN_CACHE_TTL = 500; function getSecretKey(): Uint8Array { const config = getConfig(); diff --git a/Overlord-Server/src/server/agent-auth.ts b/Overlord-Server/src/server/agent-auth.ts index 3f9a639..de07418 100644 --- a/Overlord-Server/src/server/agent-auth.ts +++ b/Overlord-Server/src/server/agent-auth.ts @@ -17,8 +17,13 @@ export function isAuthorizedAgentRequest( String(process.env.OVERLORD_DISABLE_AGENT_AUTH || "").toLowerCase() === "true"; if (disableAuth) { - logger.info("[auth] Agent auth explicitly disabled by OVERLORD_DISABLE_AGENT_AUTH=true"); - return true; + const nodeEnv = String(process.env.NODE_ENV || "development").toLowerCase(); + if (nodeEnv === "production") { + logger.warn("[auth] OVERLORD_DISABLE_AGENT_AUTH is ignored in production mode"); + } else { + logger.info("[auth] Agent auth explicitly disabled by OVERLORD_DISABLE_AGENT_AUTH=true (non-production mode)"); + return true; + } } const token = agentToken?.trim(); diff --git a/Overlord-Server/src/server/routes/deploy-routes.ts b/Overlord-Server/src/server/routes/deploy-routes.ts index 118119c..c9189be 100644 --- a/Overlord-Server/src/server/routes/deploy-routes.ts +++ b/Overlord-Server/src/server/routes/deploy-routes.ts @@ -113,6 +113,20 @@ export async function handleDeployRoutes( return new Response("Only http and https URLs are allowed", { status: 400 }); } + const hostname = parsed.hostname.toLowerCase(); + const BLOCKED_HOSTS = ["localhost", "metadata.google.internal", "169.254.169.254"]; + if ( + BLOCKED_HOSTS.includes(hostname) || + hostname.endsWith(".internal") || + hostname.startsWith("127.") || + hostname === "[::1]" || + /^(10\.|172\.(1[6-9]|2\d|3[01])\.|192\.168\.)/.test(hostname) || + hostname.startsWith("169.254.") || + hostname.startsWith("0.") + ) { + return new Response("URLs pointing to private/internal addresses are not allowed", { status: 400 }); + } + const rawFilename = path.basename(parsed.pathname) || "download.bin"; const filename = rawFilename.replace(/[^a-zA-Z0-9._\-]/g, "_").substring(0, 128) || "download.bin"; diff --git a/Overlord-Server/src/server/routes/file-download-routes.ts b/Overlord-Server/src/server/routes/file-download-routes.ts index 3728d54..25ca67f 100644 --- a/Overlord-Server/src/server/routes/file-download-routes.ts +++ b/Overlord-Server/src/server/routes/file-download-routes.ts @@ -140,7 +140,9 @@ const FILE_UPLOAD_PULL_TTL_MS = parsePositiveIntEnv( function isSafeRemotePath(value: string): boolean { if (!value || value.length > 4096) return false; - return !/[\x00-\x1F\x7F]/.test(value); + if (/[\x00-\x1F\x7F]/.test(value)) return false; + if (/(\.\.([\/\\]|$))/.test(value)) return false; + return true; } async function serveDownloadById( diff --git a/Overlord-Server/src/server/routes/ws-upgrade-routes.ts b/Overlord-Server/src/server/routes/ws-upgrade-routes.ts index e8bbc5c..bdb3b14 100644 --- a/Overlord-Server/src/server/routes/ws-upgrade-routes.ts +++ b/Overlord-Server/src/server/routes/ws-upgrade-routes.ts @@ -11,12 +11,46 @@ type WsUpgradeDeps = { isAuthorizedAgentRequest: (req: Request, url: URL) => boolean; }; +const WS_RATE_WINDOW_MS = 10_000; +const WS_RATE_MAX = 30; +const wsRateMap = new Map(); + +setInterval(() => { + const now = Date.now(); + for (const [ip, entry] of wsRateMap.entries()) { + if (now - entry.windowStart > WS_RATE_WINDOW_MS * 2) { + wsRateMap.delete(ip); + } + } +}, 30_000); + +function isWsRateLimited(ip: string): boolean { + if (!ip) return false; + const now = Date.now(); + const entry = wsRateMap.get(ip); + if (!entry || now - entry.windowStart > WS_RATE_WINDOW_MS) { + wsRateMap.set(ip, { count: 1, windowStart: now }); + return false; + } + entry.count++; + if (entry.count > WS_RATE_MAX) { + logger.warn(`[rate-limit] WebSocket upgrade rate limit exceeded for IP ${ip}`); + return true; + } + return false; +} + export async function handleWsUpgradeRoutes( req: Request, url: URL, server: RequestServer, deps: WsUpgradeDeps, ): Promise { + const ip = server.requestIP(req)?.address || ""; + if (isWsRateLimited(ip)) { + return new Response("Too Many Requests", { status: 429 }); + } + const consoleWsMatch = url.pathname.match(/^\/api\/clients\/(.+)\/console\/ws$/); if (consoleWsMatch) { const user = await authenticateRequest(req);