all repos — sz-chrome-remote-extension @ master

pi-chrome, patched to drive a signed-in browser on another host through a broker, with an MCP server

broker/upload-guard.mjs (view raw)

 1
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
// Which local files a command may hand to the browser's file inputs.
//
// The broker runs on the browser's host, so a path in an upload command names a file on that
// machine, and nothing else checks it: the companion extension passes it to
// DOM.setFileInputFiles as given. Anyone holding the broker's token could attach any file the
// user can read to a page. Clients are told to stage uploads in one directory; this enforces it.
//
// Covers chrome_upload_file (page.upload) and the same CDP method through chrome_cdp (cdp.call).
// Paths are resolved with realpath first, so a symlink or ".." cannot lead out of the directory.

import { realpathSync } from "node:fs";
import { sep } from "node:path";

export const DEFAULT_UPLOAD_DIR = "/tmp/pi-chrome-uploads";

function resolveReal(path) {
	try {
		return realpathSync(path);
	} catch {
		return undefined; // missing: nothing to upload, and not ours to guess at
	}
}

function pathsOf(action, params) {
	if (action === "page.upload") return Array.isArray(params?.paths) ? params.paths : [];
	if (action === "cdp.call" && params?.method === "DOM.setFileInputFiles") {
		const files = params.params?.files;
		return Array.isArray(files) ? files : [];
	}
	return [];
}

// Throws if a command would upload a file from outside uploadDir.
export function assertUploadsStaged(action, params, uploadDir = DEFAULT_UPLOAD_DIR) {
	const paths = pathsOf(action, params);
	if (paths.length === 0) return;
	const root = resolveReal(uploadDir);
	for (const path of paths) {
		const real = typeof path === "string" && path.startsWith("/") ? resolveReal(path) : undefined;
		if (root === undefined || real === undefined || !real.startsWith(root + sep)) {
			throw new Error(`Uploads are limited to files in ${uploadDir} on the browser's host; "${path}" is not one. Copy the file there first.`);
		}
	}
}