From e0ca073f82bdb270ce0949028ca0d9a0540bd233 Mon Sep 17 00:00:00 2001 From: fuleinist Date: Fri, 17 Apr 2026 06:06:40 +0800 Subject: [PATCH] Fix Bus.unsubscribe splice(-1) bug and add auth validation to copilot-stream endpoint Fixes two security issues: 1. CLI Bus.unsubscribe (issue #492): Fix splice(-1,1) bug when indexOf returns -1. - When unsubscribe is called twice, splice(-1,1) removes the last element instead of doing nothing. - Guard with indexOf check before splicing. 2. Copilot stream auth validation (issue #493/#494): Return 401 when Bearer token is missing. - Extract and validate apiKey before passing to controller. - Prevents undefined apiKey from bypassing auth checks. --- apps/cli/src/application/lib/bus.ts | 3 ++- .../api/copilot-stream-response/[streamId]/route.ts | 11 ++++++++++- apps/x/packages/core/src/application/lib/bus.ts | 3 ++- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/apps/cli/src/application/lib/bus.ts b/apps/cli/src/application/lib/bus.ts index 0987978e..ebbc9428 100644 --- a/apps/cli/src/application/lib/bus.ts +++ b/apps/cli/src/application/lib/bus.ts @@ -29,7 +29,8 @@ export class InMemoryBus implements IBus { } this.subscribers.get(runId)!.push(handler); return () => { - this.subscribers.get(runId)!.splice(this.subscribers.get(runId)!.indexOf(handler), 1); + const idx = this.subscribers.get(runId)!.indexOf(handler); + if (idx !== -1) this.subscribers.get(runId)!.splice(idx, 1); }; } } \ No newline at end of file diff --git a/apps/rowboat/app/api/copilot-stream-response/[streamId]/route.ts b/apps/rowboat/app/api/copilot-stream-response/[streamId]/route.ts index 620dfd00..b151f04e 100644 --- a/apps/rowboat/app/api/copilot-stream-response/[streamId]/route.ts +++ b/apps/rowboat/app/api/copilot-stream-response/[streamId]/route.ts @@ -10,6 +10,15 @@ export async function GET(request: Request, props: { params: Promise<{ streamId: // get user data const user = await requireAuth(); + // Validate auth token + const apiKey = request.headers.get("Authorization")?.split(" ")[1]; + if (!apiKey) { + return new Response(JSON.stringify({ error: "Missing or invalid Authorization header" }), { + status: 401, + headers: { "Content-Type": "application/json" }, + }); + } + const runCopilotCachedTurnController = container.resolve("runCopilotCachedTurnController"); const encoder = new TextEncoder(); @@ -21,7 +30,7 @@ export async function GET(request: Request, props: { params: Promise<{ streamId: for await (const event of runCopilotCachedTurnController.execute({ caller: "user", userId: user.id, - apiKey: request.headers.get("Authorization")?.split(" ")[1], + apiKey, key: params.streamId, })) { // Check if this is a content event diff --git a/apps/x/packages/core/src/application/lib/bus.ts b/apps/x/packages/core/src/application/lib/bus.ts index 4cf2fbb5..d7c83d97 100644 --- a/apps/x/packages/core/src/application/lib/bus.ts +++ b/apps/x/packages/core/src/application/lib/bus.ts @@ -29,7 +29,8 @@ export class InMemoryBus implements IBus { } this.subscribers.get(runId)!.push(handler); return () => { - this.subscribers.get(runId)!.splice(this.subscribers.get(runId)!.indexOf(handler), 1); + const idx = this.subscribers.get(runId)!.indexOf(handler); + if (idx !== -1) this.subscribers.get(runId)!.splice(idx, 1); }; } } \ No newline at end of file