fix(hub): reject invalid socket transport URLs

This commit is contained in:
Mohamed Boudra
2026-07-13 15:13:48 +02:00
parent 02adae8499
commit 64df1896d0
2 changed files with 46 additions and 1 deletions
@@ -73,6 +73,24 @@ test.each([408, 429])("transient enrollment status %s remains retryable", async
expect((error as Error).message).toBe(`Hub enrollment failed (${status})`);
});
test("enrollment rejects a transport URL that cannot open a WebSocket", async () => {
const hubOrigin = await startEnrollmentHub("ftp://hub.test/daemon");
const remote = new DirectHubRelationshipRemote();
await expect(
remote.enroll({
relationshipId: "relationship-1",
idempotencyKey: "ceremony-1",
hubOrigin,
token: "token",
serverId: "server-1",
daemonPublicKey: "public-key",
credentialVerifier: "verifier",
scopes: ["hub.*"],
}),
).rejects.toThrow("Hub WebSocket URL must use ws or wss");
});
test.each(["enrollment", "revocation"])("%s HTTP calls are bounded", async (operation) => {
const hubOrigin = await startStalledHub();
const remote = new DirectHubRelationshipRemote({ requestTimeoutMs: 25 });
@@ -212,6 +230,28 @@ async function startHubReturning(status: number): Promise<string> {
return `http://127.0.0.1:${address.port}`;
}
async function startEnrollmentHub(webSocketUrl: string): Promise<string> {
const server = createServer(async (request, response) => {
let body = "";
for await (const chunk of request) body += chunk;
const enrollment = JSON.parse(body) as { relationshipId: string };
response.writeHead(200, { "content-type": "application/json" }).end(
JSON.stringify({
relationshipId: enrollment.relationshipId,
scopes: ["hub.*"],
webSocketUrl,
}),
);
});
openServers.push(server);
await new Promise<void>((resolve, reject) => {
server.once("error", reject);
server.listen(0, "127.0.0.1", resolve);
});
const address = server.address() as AddressInfo;
return `http://127.0.0.1:${address.port}`;
}
async function startStalledHub(): Promise<string> {
const server = createServer(() => undefined);
openServers.push(server);
@@ -58,7 +58,12 @@ export class HubEnrollmentRejectedError extends Error {
const EnrollmentResultSchema = z.object({
relationshipId: z.string(),
scopes: z.array(z.string()),
webSocketUrl: z.string().url(),
webSocketUrl: z
.string()
.url()
.refine((value) => ["ws:", "wss:"].includes(new URL(value).protocol), {
message: "Hub WebSocket URL must use ws or wss",
}),
});
export class DirectHubRelationshipRemote implements HubRelationshipRemote {