Files
paseo/packages/protocol/src/daemon-endpoints.test.ts
T
Mohamed Boudra 793b16c0df fix(relay): protect sessions against replayed commands (#7610)
Protect relay sessions against replayed commands with authenticated Noise NK negotiation, ordered records, and identity-bound security pins. Preserve legacy admission during migration and add daemon enforcement policy.

All 21 CI checks passed. General engineering review is complete; specialist cryptographic review of the handwritten Noise implementation and static-key reuse remains required before release or deployment.

Closes #359
2026-10-22 19:50:06 +07:00

272 lines
7.9 KiB
TypeScript

import { describe, expect, test } from "vitest";
import {
buildDaemonWebSocketUrl,
buildRelayWebSocketUrl,
CURRENT_RELAY_PROTOCOL_VERSION,
extractHostPortFromWebSocketUrl,
normalizeRelayProtocolVersion,
parseConnectionUri,
parseRelayConnectionUri,
serializeRelayConnectionUri,
serializeConnectionUri,
serializeConnectionUriForStorage,
shouldUseTlsForDefaultHostedRelay,
} from "./daemon-endpoints.js";
describe("connection URI parsing", () => {
test("round-trips a tcp host and port", () => {
const parsed = parseConnectionUri("tcp://localhost:6767");
expect(parsed).toEqual({
host: "localhost",
port: 6767,
isIpv6: false,
useTls: false,
});
expect(serializeConnectionUri(parsed)).toBe("tcp://localhost:6767");
});
test("round-trips an SSL-enabled tcp host and port", () => {
const parsed = parseConnectionUri("tcp://example.com:443?ssl=true");
expect(parsed).toEqual({
host: "example.com",
port: 443,
isIpv6: false,
useTls: true,
});
expect(serializeConnectionUri(parsed)).toBe("tcp://example.com:443?ssl=true");
});
test("round-trips an IPv6 host", () => {
const parsed = parseConnectionUri("tcp://[::1]:6767?ssl=true");
expect(parsed).toEqual({
host: "::1",
port: 6767,
isIpv6: true,
useTls: true,
});
expect(serializeConnectionUri(parsed)).toBe("tcp://[::1]:6767?ssl=true");
});
test("rejects a missing port", () => {
expect(() => parseConnectionUri("tcp://localhost")).toThrow("Connection URI port is required");
});
test("rejects an invalid scheme", () => {
expect(() => parseConnectionUri("http://localhost:6767")).toThrow(
"Connection URI protocol must be tcp:",
);
});
test("parses password without including it in the public serializer", () => {
const parsed = parseConnectionUri("tcp://localhost:6767?ssl=true&password=secret");
expect(parsed).toEqual({
host: "localhost",
port: 6767,
isIpv6: false,
useTls: true,
password: "secret",
});
expect(serializeConnectionUri(parsed)).toBe("tcp://localhost:6767?ssl=true");
expect(serializeConnectionUriForStorage(parsed)).toBe(
"tcp://localhost:6767?ssl=true&password=secret",
);
});
test("rejects userinfo passwords", () => {
expect(() => parseConnectionUri("tcp://:secret@localhost:6767?ssl=true")).toThrow(
"Connection URI userinfo is not supported",
);
});
});
describe("daemon websocket URLs", () => {
test("uses ws for port 443 when TLS is disabled", () => {
expect(buildDaemonWebSocketUrl("example.com:443", { useTls: false })).toBe(
"ws://example.com:443/ws",
);
});
test("uses wss for non-443 ports when TLS is enabled", () => {
expect(buildDaemonWebSocketUrl("example.com:6767", { useTls: true })).toBe(
"wss://example.com:6767/ws",
);
});
});
describe("relay websocket URL versioning", () => {
test("defaults relay URLs to v2", () => {
const url = new URL(
buildRelayWebSocketUrl({
endpoint: "relay.paseo.sh:443",
useTls: true,
serverId: "srv_test",
role: "client",
}),
);
expect(url.searchParams.get("v")).toBe(CURRENT_RELAY_PROTOCOL_VERSION);
expect(url.searchParams.has("connectionId")).toBe(false);
});
test("includes connectionId when provided (server data sockets)", () => {
const url = new URL(
buildRelayWebSocketUrl({
endpoint: "relay.paseo.sh:443",
useTls: true,
serverId: "srv_test",
role: "server",
connectionId: "conn_abc123",
}),
);
expect(url.searchParams.get("connectionId")).toBe("conn_abc123");
});
test("allows explicitly requesting v1 relay URLs", () => {
const url = new URL(
buildRelayWebSocketUrl({
endpoint: "relay.paseo.sh:443",
useTls: true,
serverId: "srv_test",
role: "server",
version: "1",
}),
);
expect(url.searchParams.get("v")).toBe("1");
});
test("normalizes numeric relay versions", () => {
expect(normalizeRelayProtocolVersion(2)).toBe("2");
expect(normalizeRelayProtocolVersion(1)).toBe("1");
});
test("rejects unsupported relay versions", () => {
expect(() => normalizeRelayProtocolVersion("3")).toThrow('Relay version must be "1" or "2"');
});
});
describe("relay websocket URLs", () => {
test("uses ws for port 443 when TLS is disabled", () => {
const url = new URL(
buildRelayWebSocketUrl({
endpoint: "relay.paseo.sh:443",
useTls: false,
serverId: "srv_test",
role: "client",
}),
);
expect(url.protocol).toBe("ws:");
});
test("uses wss for non-443 ports when TLS is enabled", () => {
const url = new URL(
buildRelayWebSocketUrl({
endpoint: "relay.paseo.sh:6767",
useTls: true,
serverId: "srv_test",
role: "client",
}),
);
expect(url.protocol).toBe("wss:");
});
test("round-trips IPv6 relay endpoints with TLS enabled", () => {
const wsUrl = buildRelayWebSocketUrl({
endpoint: "[::1]:443",
useTls: true,
serverId: "srv_test",
role: "client",
});
const url = new URL(wsUrl);
expect(url.protocol).toBe("wss:");
expect(extractHostPortFromWebSocketUrl(wsUrl)).toBe("[::1]:443");
});
});
describe("shouldUseTlsForDefaultHostedRelay", () => {
test("returns true for the hosted Paseo relay on port 443", () => {
expect(shouldUseTlsForDefaultHostedRelay("relay.paseo.sh:443")).toBe(true);
});
test("returns true for any self-hosted relay on port 443", () => {
expect(shouldUseTlsForDefaultHostedRelay("relay.example.com:443")).toBe(true);
});
test("returns true for an IPv6 relay on port 443", () => {
expect(shouldUseTlsForDefaultHostedRelay("[::1]:443")).toBe(true);
});
test("returns false for a relay on a non-443 port", () => {
expect(shouldUseTlsForDefaultHostedRelay("relay.example.com:8080")).toBe(false);
});
test("returns false for malformed endpoints", () => {
expect(shouldUseTlsForDefaultHostedRelay("not-an-endpoint")).toBe(false);
});
});
describe("relay connection URI", () => {
test("round-trips the offer and password through a direct URI and connect wrapper", () => {
const parts = {
offer: {
v: 2 as const,
serverId: "srv_test",
daemonPublicKeyB64: "abc+/=",
relay: { endpoint: "relay.paseo.sh:443", useTls: true },
},
password: "two words",
};
const uri = serializeRelayConnectionUri(parts);
expect(parseRelayConnectionUri(uri)).toEqual(parts);
expect(
parseRelayConnectionUri(`https://app.paseo.sh/#connect=${encodeURIComponent(uri)}`),
).toEqual(parts);
});
});
describe("relay connection URI security mode", () => {
test("round-trips a protected minimum security mode", () => {
const uri = serializeRelayConnectionUri({
offer: {
v: 2,
serverId: "srv_test",
daemonPublicKeyB64: "key",
relay: { endpoint: "relay.example.com:443", useTls: true },
minimumSecurityMode: "protected",
},
});
expect(new URL(uri).searchParams.get("security")).toBe("protected");
expect(parseRelayConnectionUri(uri).offer.minimumSecurityMode).toBe("protected");
});
test("omits the security parameter when the offer has no minimum", () => {
const uri = serializeRelayConnectionUri({
offer: {
v: 2,
serverId: "srv_test",
daemonPublicKeyB64: "key",
relay: { endpoint: "relay.example.com:443", useTls: true },
},
});
expect(new URL(uri).searchParams.has("security")).toBe(false);
expect(parseRelayConnectionUri(uri).offer).not.toHaveProperty("minimumSecurityMode");
});
test("rejects an unknown security mode instead of silently downgrading", () => {
expect(() =>
parseRelayConnectionUri("relay://relay.example.com:443/srv_test?key=k&security=legacy"),
).toThrow("unsupported security mode");
});
});