fix: authenticate Workbench SSE inspection

This commit is contained in:
root
2026-07-20 21:25:30 +02:00
parent add8f564dc
commit 5a18088249
2 changed files with 33 additions and 4 deletions
+28 -2
View File
@@ -177,7 +177,14 @@ describe("Workbench native HTTP adapter", () => {
async dispatch() { return { ok: true }; },
async snapshot() { return { sessions: {}, turns: {} }; }
});
const server = Bun.serve({ port: 0, fetch: (request) => app.fetch(request) });
let authorization: string | null = null;
const server = Bun.serve({
port: 0,
fetch(request) {
authorization = request.headers.get("authorization");
return app.fetch(request);
}
});
try {
const pending = runWorkbenchCli([
"events", "inspect",
@@ -186,7 +193,7 @@ describe("Workbench native HTTP adapter", () => {
"--over-api",
"--api-url", `http://127.0.0.1:${server.port}`,
"--timeout-ms", "1000"
], {});
], { HWLAB_API_KEY: "l0-sse-api-key" });
for (let index = 0; index < 20 && subscribers.size === 0; index += 1) await Bun.sleep(5);
for (const listener of subscribers) listener({ schema: "hwlab.event.v1", sessionId: "ses_l0_sse", traceId: "trc_l0_sse", event: { type: "backend", eventType: "backend", label: "agentrun:backend:command-created" } });
expect(await pending).toMatchObject({
@@ -203,12 +210,31 @@ describe("Workbench native HTTP adapter", () => {
eventCount: 1,
eventTypes: ["backend"]
});
expect(authorization).toBe("Bearer l0-sse-api-key");
} finally {
server.stop(true);
await app.close();
}
});
test("L0 CLI reports the Workbench SSE HTTP status when authentication fails", async () => {
const server = Bun.serve({ port: 0, fetch: () => new Response("unauthorized", { status: 401 }) });
try {
await expect(runWorkbenchCli([
"events", "inspect",
"--trace-id", "trc_l0_auth_failure",
"--over-api",
"--api-url", `http://127.0.0.1:${server.port}`,
"--timeout-ms", "1000"
], {})).rejects.toMatchObject({
code: "workbench_sse_read_failed",
message: expect.stringContaining("Workbench SSE returned HTTP 401")
});
} finally {
server.stop(true);
}
});
test("L0 CLI replays retained Kafka events before the same live SSE stream", async () => {
const sessionId = "ses_l0_refresh";
const traceId = "trc_l0_refresh";
+5 -2
View File
@@ -55,10 +55,13 @@ async function inspectEvents(parsed: Parsed, env: Record<string, string | undefi
const path = `/v1/workbench/events?${params.toString()}`;
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeoutMs);
const headers: Record<string, string> = {};
const apiKey = env.HWLAB_API_KEY;
if (apiKey) headers.authorization = `Bearer ${apiKey}`;
const frames: SseFrame[] = [];
let buffer = "";
try {
const response = await fetch(`${baseUrl.replace(/\/$/u, "")}${path}`, { signal: controller.signal });
const response = await fetch(`${baseUrl.replace(/\/$/u, "")}${path}`, { signal: controller.signal, headers });
if (!response.ok || !response.body) throw codedError("workbench_sse_open_failed", `Workbench SSE returned HTTP ${response.status}`);
const reader = response.body.getReader();
const decoder = new TextDecoder();
@@ -83,7 +86,7 @@ async function inspectEvents(parsed: Parsed, env: Record<string, string | undefi
const business = businessFrames(frames);
if (error?.name !== "AbortError" && !(error?.code === "ECONNRESET" && framesInspectionSatisfied(frames, minEvents, waitFor))) {
const observed = semanticSummary(business);
throw codedError("workbench_sse_read_failed", `Workbench SSE read failed: ${error?.code ?? error?.name ?? "unknown"}; frames=${frames.length}; businessEvents=${business.length}; observed=${observed.observedSemantics.join(",")}; connected=${frames.some((entry) => entry.name === "workbench.connected")}`);
throw codedError("workbench_sse_read_failed", `Workbench SSE read failed: ${error?.message ?? error?.code ?? error?.name ?? "unknown"}; frames=${frames.length}; businessEvents=${business.length}; observed=${observed.observedSemantics.join(",")}; connected=${frames.some((entry) => entry.name === "workbench.connected")}`);
}
} finally {
clearTimeout(timer);