fix(security): shell injection, symlink escape, remove /pairing endpoint, harden defaults
- Replace all execAsync shell-interpolated git calls with execFileAsync + array args in session.ts - Add symlink resolution in file-explorer resolveScopedPath to prevent workspace escape - Remove /pairing HTTP endpoint from server; desktop now uses `paseo daemon pair --json` via CLI - Disable MCP HTTP endpoint by default (opt-in via config) - Correct SECURITY.md: fix cipher name (XSalsa20-Poly1305), accurate replay resistance claims, add local daemon trust boundary docs
This commit is contained in:
@@ -17,7 +17,11 @@ import {
|
||||
sendLocalTransportMessage,
|
||||
closeLocalTransportSession,
|
||||
} from "./local-transport.js";
|
||||
import { createNodeEntrypointInvocation, resolveDaemonRunnerEntrypoint } from "./runtime-paths.js";
|
||||
import {
|
||||
createNodeEntrypointInvocation,
|
||||
resolveDaemonRunnerEntrypoint,
|
||||
runCliJsonCommand,
|
||||
} from "./runtime-paths.js";
|
||||
|
||||
const DAEMON_LOG_FILENAME = "daemon.log";
|
||||
const DAEMON_PID_FILENAME = "paseo.pid";
|
||||
@@ -405,20 +409,7 @@ async function getDaemonPairing(): Promise<DesktopPairingOffer> {
|
||||
}
|
||||
|
||||
try {
|
||||
if (!status.listen) {
|
||||
throw new Error("Daemon listen target is unavailable.");
|
||||
}
|
||||
const baseUrl = buildDaemonHttpBaseUrl(status.listen);
|
||||
if (!baseUrl) {
|
||||
throw new Error(`Daemon listen target is not a TCP endpoint: ${status.listen}`);
|
||||
}
|
||||
|
||||
const response = await fetch(`${baseUrl}/pairing`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Daemon pairing request failed with ${response.status}`);
|
||||
}
|
||||
|
||||
const payload = (await response.json()) as unknown;
|
||||
const payload = runCliJsonCommand(["daemon", "pair", "--json"]);
|
||||
if (!isRecord(payload)) {
|
||||
throw new Error("Daemon pairing response was not an object.");
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { spawnSync, type SpawnSyncReturns } from "node:child_process";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { createRequire } from "node:module";
|
||||
import path from "node:path";
|
||||
import { app } from "electron";
|
||||
@@ -219,23 +219,22 @@ export function createNodeEntrypointInvocation(input: {
|
||||
});
|
||||
}
|
||||
|
||||
function spawnCliProcess(args: string[]): SpawnSyncReturns<Buffer> {
|
||||
function createCliInvocation(args: string[]): NodeEntrypointInvocation {
|
||||
const cli = resolveCliEntrypoint();
|
||||
const invocation = createNodeEntrypointInvocation({
|
||||
return createNodeEntrypointInvocation({
|
||||
entrypoint: cli,
|
||||
argvMode: "bare",
|
||||
args,
|
||||
baseEnv: process.env,
|
||||
});
|
||||
|
||||
return spawnSync(invocation.command, invocation.args, {
|
||||
env: invocation.env,
|
||||
stdio: "inherit",
|
||||
});
|
||||
}
|
||||
|
||||
export function runCliPassthroughCommand(args: string[]): number {
|
||||
const result = spawnCliProcess(args);
|
||||
const invocation = createCliInvocation(args);
|
||||
const result = spawnSync(invocation.command, invocation.args, {
|
||||
env: invocation.env,
|
||||
stdio: "inherit",
|
||||
});
|
||||
if (result.error) {
|
||||
throw result.error;
|
||||
}
|
||||
@@ -246,3 +245,34 @@ export function runCliPassthroughCommand(args: string[]): number {
|
||||
|
||||
return result.signal ? 1 : 0;
|
||||
}
|
||||
|
||||
export function runCliJsonCommand(args: string[]): unknown {
|
||||
const invocation = createCliInvocation(args);
|
||||
const result = spawnSync(invocation.command, invocation.args, {
|
||||
env: invocation.env,
|
||||
encoding: "utf-8",
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
throw result.error;
|
||||
}
|
||||
|
||||
if (result.status !== 0) {
|
||||
const stderr = typeof result.stderr === "string" ? result.stderr.trim() : "";
|
||||
throw new Error(stderr.length > 0 ? stderr : `CLI command failed with exit code ${result.status}`);
|
||||
}
|
||||
|
||||
const stdout = typeof result.stdout === "string" ? result.stdout.trim() : "";
|
||||
if (stdout.length === 0) {
|
||||
throw new Error("CLI command did not produce JSON output.");
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(stdout) as unknown;
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`CLI command returned invalid JSON: ${error instanceof Error ? error.message : String(error)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user