From bfc3e3fa4dba636bde126d71983848b6c5e1e53e Mon Sep 17 00:00:00 2001 From: KingKDot Date: Mon, 13 Apr 2026 17:33:51 -0500 Subject: [PATCH] more security improvements --- .../src/server/notification-delivery.ts | 15 ++++++++++++++ .../src/server/routes/plugin-routes.ts | 20 +++++++++++++++++-- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/Overlord-Server/src/server/notification-delivery.ts b/Overlord-Server/src/server/notification-delivery.ts index 858b8ae..6e6dd2f 100644 --- a/Overlord-Server/src/server/notification-delivery.ts +++ b/Overlord-Server/src/server/notification-delivery.ts @@ -177,6 +177,21 @@ async function deliverToUserWebhook( return; } + 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.") + ) { + logger.warn(`[notify] blocked webhook to private/internal address: ${hostname}`); + return; + } + try { const isDiscord = /discord(app)?\.com$/i.test(parsed.hostname); if (isDiscord) { diff --git a/Overlord-Server/src/server/routes/plugin-routes.ts b/Overlord-Server/src/server/routes/plugin-routes.ts index d665e01..6cb6e14 100644 --- a/Overlord-Server/src/server/routes/plugin-routes.ts +++ b/Overlord-Server/src/server/routes/plugin-routes.ts @@ -3,6 +3,7 @@ import path from "path"; import { v4 as uuidv4 } from "uuid"; import { authenticateRequest } from "../../auth"; import { requirePermission } from "../../rbac"; +import { canUserAccessClient } from "../../users"; import * as clientManager from "../../clientManager"; import { metrics } from "../../metrics"; import { encodeMessage } from "../../protocol"; @@ -88,6 +89,9 @@ export async function handlePluginRoutes( } const clientId = clientPluginsMatch[1]; + if (!canUserAccessClient(user.userId, user.role, clientId)) { + return new Response("Forbidden: You do not have access to this client", { status: 403 }); + } const loaded = deps.pluginLoadedByClient.get(clientId) || new Set(); const manifests = await deps.listPluginManifests(); const plugins = manifests.map((manifest) => ({ @@ -337,8 +341,8 @@ export async function handlePluginRoutes( if (req.method === "POST" && pluginExecMatch) { const user = await authenticateRequest(req); if (!user) return new Response("Unauthorized", { status: 401 }); - if (user.role !== "admin" && user.role !== "operator") { - return new Response("Forbidden: Admin or operator access required", { status: 403 }); + if (user.role !== "admin") { + return new Response("Forbidden: Admin access required", { status: 403 }); } let pluginId = ""; try { pluginId = deps.sanitizePluginId(pluginExecMatch[1]); } catch { return new Response("Invalid plugin id", { status: 400 }); } @@ -458,6 +462,9 @@ export async function handlePluginRoutes( return new Response("Forbidden", { status: 403 }); } const targetId = pluginLoadMatch[1]; + if (!canUserAccessClient(user.userId, user.role, targetId)) { + return new Response("Forbidden: You do not have access to this client", { status: 403 }); + } const pluginId = pluginLoadMatch[2]; const target = clientManager.getClient(targetId); if (!target) return new Response("Not found", { status: 404 }); @@ -489,6 +496,9 @@ export async function handlePluginRoutes( return new Response("Forbidden", { status: 403 }); } const targetId = pluginEventsPollMatch[1]; + if (!canUserAccessClient(user.userId, user.role, targetId)) { + return new Response("Forbidden: You do not have access to this client", { status: 403 }); + } const pluginId = pluginEventsPollMatch[2]; const events = deps.drainPluginUIEvents(targetId, pluginId); return Response.json({ events }); @@ -506,6 +516,9 @@ export async function handlePluginRoutes( } const targetId = pluginEventMatch[1]; + if (!canUserAccessClient(user.userId, user.role, targetId)) { + return new Response("Forbidden: You do not have access to this client", { status: 403 }); + } const pluginId = pluginEventMatch[2]; const target = clientManager.getClient(targetId); if (!target) return new Response("Not found", { status: 404 }); @@ -565,6 +578,9 @@ export async function handlePluginRoutes( } const targetId = pluginUnloadMatch[1]; + if (!canUserAccessClient(user.userId, user.role, targetId)) { + return new Response("Forbidden: You do not have access to this client", { status: 403 }); + } const pluginId = pluginUnloadMatch[2]; const target = clientManager.getClient(targetId); if (!target) return new Response("Not found", { status: 404 });