test
This commit is contained in:
@@ -214,6 +214,7 @@ export const COMMAND_TYPES = [
|
||||
"file_search",
|
||||
"file_thumb",
|
||||
"file_upload",
|
||||
"file_upload_desktop",
|
||||
"file_upload_http",
|
||||
"file_write",
|
||||
"file_zip",
|
||||
@@ -602,6 +603,10 @@ export const COMMAND_VERSION_SUPPORT = {
|
||||
"min": 1,
|
||||
"max": 1
|
||||
},
|
||||
"file_upload_desktop": {
|
||||
"min": 1,
|
||||
"max": 1
|
||||
},
|
||||
"file_upload_http": {
|
||||
"min": 1,
|
||||
"max": 1
|
||||
|
||||
@@ -17,9 +17,10 @@ import {
|
||||
describe("generated wire protocol contract", () => {
|
||||
test("publishes the complete command catalog", () => {
|
||||
expect(WIRE_PROTOCOL_VERSION).toBe(1);
|
||||
expect(COMMAND_TYPES.length).toBe(142);
|
||||
expect(COMMAND_TYPES.length).toBe(143);
|
||||
expect(new Set(COMMAND_TYPES).size).toBe(COMMAND_TYPES.length);
|
||||
expect(isCommandType("desktop_start")).toBe(true);
|
||||
expect(isCommandType("file_upload_desktop")).toBe(true);
|
||||
expect(isCommandType("virtual_window_list")).toBe(true);
|
||||
expect(isCommandType("not_a_command")).toBe(false);
|
||||
});
|
||||
|
||||
@@ -35,4 +35,19 @@ describe("file browser command payload validation", () => {
|
||||
path: "C:\\ok.bin", url: "https://attacker.invalid/payload", total: 10,
|
||||
})).toBeNull();
|
||||
});
|
||||
|
||||
test("validates remote desktop uploads as safe basenames", () => {
|
||||
const valid = {
|
||||
fileName: "report.pdf",
|
||||
url: "/api/file/upload/pull/123e4567-e89b-42d3-a456-426614174000",
|
||||
total: 10,
|
||||
};
|
||||
expect(validateFileBrowserCommandPayload("file_upload_desktop", valid)).not.toBeNull();
|
||||
expect(validateFileBrowserCommandPayload("file_upload_desktop", { ...valid, display: 1, x: 640, y: 360 })).not.toBeNull();
|
||||
expect(validateFileBrowserCommandPayload("file_upload_desktop", { ...valid, x: 640 })).toBeNull();
|
||||
expect(validateFileBrowserCommandPayload("file_upload_desktop", { ...valid, x: 640.5, y: 360 })).toBeNull();
|
||||
expect(validateFileBrowserCommandPayload("file_upload_desktop", { ...valid, fileName: "../report.pdf" })).toBeNull();
|
||||
expect(validateFileBrowserCommandPayload("file_upload_desktop", { ...valid, fileName: "folder/report.pdf" })).toBeNull();
|
||||
expect(validateFileBrowserCommandPayload("file_upload_desktop", { ...valid, url: "https://attacker.invalid/file" })).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -80,6 +80,17 @@ export function validateFileBrowserCommandPayload(
|
||||
|| !/^\/api\/file\/upload\/pull\/[0-9a-f-]{36}$/i.test(payload.url)) return null;
|
||||
if (!Number.isSafeInteger(Number(payload.total)) || Number(payload.total) < 0) return null;
|
||||
return payload;
|
||||
case "file_upload_desktop":
|
||||
if (!safeString(payload.fileName, 255) || /[\\/]/.test(payload.fileName as string)
|
||||
|| payload.fileName === "." || payload.fileName === ".."
|
||||
|| typeof payload.url !== "string"
|
||||
|| !/^\/api\/file\/upload\/pull\/[0-9a-f-]{36}$/i.test(payload.url)) return null;
|
||||
if (!Number.isSafeInteger(Number(payload.total)) || Number(payload.total) < 0) return null;
|
||||
if ((payload.x === undefined) !== (payload.y === undefined)) return null;
|
||||
if (payload.x !== undefined && (!Number.isSafeInteger(Number(payload.x)) || Math.abs(Number(payload.x)) > 1_000_000)) return null;
|
||||
if (payload.y !== undefined && (!Number.isSafeInteger(Number(payload.y)) || Math.abs(Number(payload.y)) > 1_000_000)) return null;
|
||||
if (payload.display !== undefined && (!Number.isSafeInteger(Number(payload.display)) || Number(payload.display) < 0 || Number(payload.display) > 128)) return null;
|
||||
return payload;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { AuditAction, logAudit } from "../auditLog";
|
||||
import * as clientManager from "../clientManager";
|
||||
import { logger } from "../logger";
|
||||
import { metrics } from "../metrics";
|
||||
import { versionCommandForClient } from "../command-compatibility";
|
||||
import { encodeMessage } from "../protocol";
|
||||
import { getSessionByTokenHash } from "../db";
|
||||
import * as sessionManager from "../sessions/sessionManager";
|
||||
@@ -251,6 +252,35 @@ export function handleFileBrowserViewerMessage(ws: ServerWebSocket<SocketData>,
|
||||
success: true,
|
||||
});
|
||||
break;
|
||||
case "file_upload_desktop":
|
||||
try {
|
||||
target.ws.send(encodeMessage(versionCommandForClient(target, {
|
||||
type: "command",
|
||||
commandType: "file_upload_desktop",
|
||||
id: routedId,
|
||||
payload: actualPayload,
|
||||
} as any)));
|
||||
} catch (error) {
|
||||
finishFileBrowserCommand(routedId);
|
||||
safeSendViewer(ws, {
|
||||
type: "command_result",
|
||||
commandId: routedId,
|
||||
ok: false,
|
||||
message: error instanceof Error ? error.message : "Desktop upload is unsupported by this client",
|
||||
});
|
||||
break;
|
||||
}
|
||||
metrics.recordCommand("file_upload_desktop");
|
||||
logAudit({
|
||||
timestamp: Date.now(),
|
||||
username: (ws.data as any).username || "unknown",
|
||||
ip: ws.data.ip || "unknown",
|
||||
action: AuditAction.FILE_UPLOAD,
|
||||
targetClientId: clientId,
|
||||
details: JSON.stringify({ fileName: actualPayload.fileName || "", destination: "remote_pointer", display: actualPayload.display, x: actualPayload.x, y: actualPayload.y, mode: "http_pull" }),
|
||||
success: true,
|
||||
});
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user