fix(hosts): validate safe targets and verify encrypted relay routes

This commit is contained in:
Mohamed Boudra
2026-10-21 10:33:11 +02:00
parent 3b50636e14
commit 32fbcdc4a8
15 changed files with 412 additions and 57 deletions
+3 -3
View File
@@ -42,15 +42,15 @@ export function buildDaemonConnectionCommandError(options: ConnectOptions & { er
if (error && typeof error === "object" && "message" in error) message = String(error.message);
if (options.target.kind === "endpoint")
message = message.replaceAll(options.target.host, describeDaemonTarget(options.target));
if (message.startsWith("Cannot connect to daemon at "))
return error as { code: string; message: string; details: string };
let code = "DAEMON_UNREACHABLE";
if (typeof error === "object" && error !== null && "code" in error) code = String(error.code);
else if (message === "Password required") code = "AUTH_REQUIRED";
else if (message === "Incorrect password") code = "AUTH_FAILED";
return {
code,
message: `Cannot connect to daemon at ${describeDaemonTarget(options.target)}: ${message}`,
message: message.startsWith("Cannot connect to daemon at ")
? message
: `Cannot connect to daemon at ${describeDaemonTarget(options.target)}: ${message}`,
details: describeConnectionRemedy(code, options.target),
};
}
+17 -1
View File
@@ -1,6 +1,10 @@
import { test, expect } from "vitest";
import { selectDaemonTarget, describeDaemonTarget } from "./daemon-target.js";
import { resolveDaemonCredential, resolveClientPaseoHome } from "./client.js";
import {
resolveDaemonCredential,
resolveClientPaseoHome,
buildDaemonConnectionCommandError,
} from "./client.js";
import { mkdtemp, rm, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { tmpdir } from "node:os";
@@ -44,6 +48,18 @@ test("endpoint descriptions redact pairing material and credentials", () => {
).not.toContain("private");
});
test("malformed endpoint diagnostics never expose the supplied credential", () => {
const target = { kind: "endpoint" as const, host: "alice:example-secret@localhost:6767" };
expect(describeDaemonTarget(target)).not.toContain("example-secret");
for (const message of [
`Invalid connection ${target.host}`,
`Cannot connect to daemon at ${target.host}`,
]) {
const diagnostic = buildDaemonConnectionCommandError({ target, error: new Error(message) });
expect(JSON.stringify(diagnostic)).not.toContain("example-secret");
}
});
test("CLI selects an explicit password before a matching local credential and never sends the latter remotely", async () => {
const home = await mkdtemp(join(tmpdir(), "paseo-cli-credential-"));
const previousPassword = process.env.PASEO_PASSWORD;
+3 -7
View File
@@ -1,3 +1,4 @@
import { parseHostTarget } from "@getpaseo/protocol/host-target";
import { resolvePaseoHome } from "@getpaseo/server/daemon-control";
export type DaemonTarget = { kind: "instance"; home: string } | { kind: "endpoint"; host: string };
@@ -39,13 +40,8 @@ export function selectDaemonTarget(
export function describeDaemonTarget(target: DaemonTarget): string {
if (target.kind === "instance") return `home ${target.home}`;
try {
const url = new URL(target.host);
if (url.password) url.password = "REDACTED";
for (const key of url.searchParams.keys())
if (/password|token|secret/i.test(key)) url.searchParams.set(key, "REDACTED");
if (url.hash) url.hash = "REDACTED";
return url.toString();
return parseHostTarget(target.host).description;
} catch {
return target.host.replace(/([?&](?:password|token|secret)=)[^&]*/gi, "$1REDACTED");
return "<invalid endpoint>";
}
}