mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-07 17:04:42 +00:00
fix(hub): validate persisted origins
This commit is contained in:
@@ -227,6 +227,38 @@ describe("Hub relationship", () => {
|
||||
},
|
||||
);
|
||||
|
||||
test.each([
|
||||
["a non-HTTP scheme", "ftp://hub.test"],
|
||||
["embedded credentials", "https://user:password@hub.test"],
|
||||
["a query", "https://hub.test?token=secret"],
|
||||
["a fragment", "https://hub.test#secret"],
|
||||
])("a persisted Hub origin with %s is quarantined before startup", async (_, hubOrigin) => {
|
||||
relationship = await HubRelationshipHarness.start();
|
||||
await relationship.corruptRelationshipFile(
|
||||
JSON.stringify({
|
||||
version: 1,
|
||||
state: "pending",
|
||||
relationship: {
|
||||
daemonId: "daemon-1",
|
||||
idempotencyKey: "ceremony-1",
|
||||
hubOrigin,
|
||||
createdAt: "2026-07-13T00:00:00.000Z",
|
||||
scopes: ["hub.execution.*"],
|
||||
},
|
||||
credential: { secret: "credential" },
|
||||
enrollment: { token: "enrollment-token" },
|
||||
identity: { serverId: "server-1", daemonPublicKey: "public-key" },
|
||||
}),
|
||||
);
|
||||
|
||||
await relationship.startStoppedDaemon();
|
||||
|
||||
expect(await relationship.status()).toMatchObject({ state: "not_connected" });
|
||||
expect(relationship.relationshipFile()).toBeNull();
|
||||
expect(await relationship.quarantinedRelationshipFiles()).toHaveLength(1);
|
||||
expect(relationship.enrollmentAttempts()).toEqual([]);
|
||||
});
|
||||
|
||||
test("a persisted non-WebSocket transport is quarantined before daemon startup", async () => {
|
||||
relationship = await HubRelationshipHarness.start();
|
||||
await relationship.corruptRelationshipFile(
|
||||
|
||||
@@ -16,11 +16,24 @@ import { BoundedExponentialHubRetryPolicy } from "./relationship-retry.js";
|
||||
|
||||
const FILE_NAME = "hub-relationship.json";
|
||||
const SCOPES = ["hub.execution.*"] as const;
|
||||
const HubOriginSchema = z
|
||||
.string()
|
||||
.url()
|
||||
.superRefine((value, context) => {
|
||||
try {
|
||||
normalizeHubUrl(value);
|
||||
} catch (error) {
|
||||
context.addIssue({
|
||||
code: "custom",
|
||||
message: error instanceof Error ? error.message : "Invalid Hub URL",
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const RelationshipSchema = z.object({
|
||||
daemonId: z.string().min(1),
|
||||
idempotencyKey: z.string().min(1),
|
||||
hubOrigin: z.string().url(),
|
||||
hubOrigin: HubOriginSchema,
|
||||
createdAt: z.string(),
|
||||
scopes: z.array(z.string()),
|
||||
});
|
||||
@@ -153,9 +166,10 @@ function normalizeHubUrl(value: string): string {
|
||||
if (url.username || url.password) {
|
||||
throw new Error("Hub URL cannot include credentials");
|
||||
}
|
||||
if (url.search || url.hash) {
|
||||
throw new Error("Hub URL cannot include a query or fragment");
|
||||
}
|
||||
url.pathname = url.pathname.replace(/\/$/, "");
|
||||
url.search = "";
|
||||
url.hash = "";
|
||||
return url.toString().replace(/\/$/, "");
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user