diff --git a/server/src/autonomy/routes.ts b/server/src/autonomy/routes.ts index dfbcb4f..886f8c9 100644 --- a/server/src/autonomy/routes.ts +++ b/server/src/autonomy/routes.ts @@ -379,14 +379,15 @@ export function createExternalChannelIngressRoutes( ) { const routes = new Hono(); routes.post("/:connectionId", async (context) => { + const connectionId = context.req.param("connectionId"); + if (!UUID_PATTERN.test(connectionId)) { + return context.json({ error: "Channel bridge not found." }, 404); + } try { - const result = await channels.ingestHttp( - context.req.param("connectionId"), - { - headers: context.req.raw.headers, - rawBody: await context.req.text(), - }, - ); + const result = await channels.ingestHttp(connectionId, { + headers: context.req.raw.headers, + rawBody: await context.req.text(), + }); if ("protocolResponse" in result) { return context.json(result.protocolResponse); } @@ -407,6 +408,9 @@ export function createExternalChannelIngressRoutes( return routes; } +const UUID_PATTERN = + /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; + async function objectBody(context: Context) { const value = await context.req.json().catch(() => null); return plainObject(value) ? value : null; diff --git a/server/tests/autonomy-routes.test.ts b/server/tests/autonomy-routes.test.ts index a4604cd..6697df5 100644 --- a/server/tests/autonomy-routes.test.ts +++ b/server/tests/autonomy-routes.test.ts @@ -41,6 +41,8 @@ function services(overrides: Record = {}) { } describe("autonomy routes", () => { + const bridgeId = "6e23d07c-389a-4d03-a6b8-fccec38cb24b"; + test("keeps external-provider creation administrative", async () => { let called = false; const routes = createAutonomyRoutes( @@ -84,7 +86,7 @@ describe("autonomy routes", () => { }, } as never); const rawBody = '{"update_id":42,"message":{"text":"hello"}}'; - const response = await routes.request("http://openbot.test/connection-1", { + const response = await routes.request(`http://openbot.test/${bridgeId}`, { method: "POST", headers: { "x-telegram-bot-api-secret-token": "provider-secret" }, body: rawBody, @@ -92,7 +94,7 @@ describe("autonomy routes", () => { expect(response.status).toBe(202); expect(seen).toEqual([ { - connectionId: "connection-1", + connectionId: bridgeId, rawBody, secret: "provider-secret", }, @@ -105,7 +107,7 @@ describe("autonomy routes", () => { throw new WorkConflictError("This external identity is not paired."); }, } as never); - const response = await routes.request("http://openbot.test/connection-1", { + const response = await routes.request(`http://openbot.test/${bridgeId}`, { method: "POST", body: "{}", }); @@ -114,4 +116,20 @@ describe("autonomy routes", () => { error: "This external identity is not paired.", }); }); + + test("rejects malformed bridge IDs before querying storage", async () => { + let called = false; + const routes = createExternalChannelIngressRoutes({ + async ingestHttp() { + called = true; + return { accepted: true, duplicate: false, runId: "run-1" }; + }, + } as never); + const response = await routes.request("http://openbot.test/not-a-uuid", { + method: "POST", + body: "{}", + }); + expect(response.status).toBe(404); + expect(called).toBe(false); + }); });