From e5b442a48036382cb4eb4781dd7d11a20e1a27d0 Mon Sep 17 00:00:00 2001 From: w287346141 <57440615+w287346141@users.noreply.github.com> Date: Mon, 1 Jun 2026 11:45:58 +0800 Subject: [PATCH] Make dashboard auth diagnostics ASCII-safe --- src/server/index.ts | 6 +++--- tests/dashboard-auth-token.test.ts | 5 +++++ tests/dashboard-host-and-token.test.ts | 18 +++++++++++++++--- 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/server/index.ts b/src/server/index.ts index fbb7d26..df06447 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -65,7 +65,7 @@ export function checkAuth( status: 403, body: JSON.stringify({ error: - "mutation requires X-Carboncode-Token header (CSRF defence — query token alone is rejected for POST/DELETE).", + "mutation requires X-Carboncode-Token header (CSRF defence; query token alone is rejected for POST/DELETE).", }), }; } @@ -125,7 +125,7 @@ export async function dispatch( const fail = checkAuth(req, expectedToken, false); if (fail) { res.writeHead(fail.status, { "content-type": "text/plain" }); - res.end("unauthorized — open the URL printed by /dashboard, including ?token=…"); + res.end("unauthorized: open the URL printed by /dashboard, including ?token=..."); return; } const html = renderIndexHtml(expectedToken, ctx.mode); @@ -220,7 +220,7 @@ export function startDashboardServer( const url = `http://${host}:${finalPort}/?token=${token}`; if (!LOOPBACK_HOSTS.has(host)) { process.stderr.write( - `▲ Dashboard bound to ${host}:${finalPort} (non-loopback). The URL token is the only auth — keep it secret.\n`, + `WARNING: Dashboard bound to ${host}:${finalPort} (non-loopback). The URL token is the only auth; keep it secret.\n`, ); } diff --git a/tests/dashboard-auth-token.test.ts b/tests/dashboard-auth-token.test.ts index de4ad30..557becd 100644 --- a/tests/dashboard-auth-token.test.ts +++ b/tests/dashboard-auth-token.test.ts @@ -8,6 +8,10 @@ function req(url: string, headers: Record = {}): IncomingMessage return { url, headers } as IncomingMessage; } +function isAscii(text: string): boolean { + return [...text].every((ch) => ch.charCodeAt(0) <= 0x7f); +} + describe("dashboard auth token headers", () => { it("accepts X-Carboncode-Token for mutations", () => { expect( @@ -24,6 +28,7 @@ describe("dashboard auth token headers", () => { expect(result?.status).toBe(403); expect(result?.body).toContain("X-Carboncode-Token"); + expect(isAscii(result?.body ?? "")).toBe(true); }); it("accepts query tokens for reads", () => { diff --git a/tests/dashboard-host-and-token.test.ts b/tests/dashboard-host-and-token.test.ts index 3731fb5..ae014ec 100644 --- a/tests/dashboard-host-and-token.test.ts +++ b/tests/dashboard-host-and-token.test.ts @@ -16,6 +16,10 @@ function ctx(dir: string) { }; } +function isAscii(text: string): boolean { + return [...text].every((ch) => ch.charCodeAt(0) <= 0x7f); +} + describe("startDashboardServer host + token (#968)", () => { let dir: string; let handle: DashboardServerHandle | undefined; @@ -36,7 +40,9 @@ describe("startDashboardServer host + token (#968)", () => { it("defaults to 127.0.0.1 when no host is given and emits no LAN warning", async () => { handle = await startDashboardServer(ctx(dir), { token: TOKEN }); expect(handle.url).toMatch(/^http:\/\/127\.0\.0\.1:\d+\/\?token=/); - const warnings = writeSpy.mock.calls.map((c) => String(c[0])).filter((s) => s.includes("▲")); + const warnings = writeSpy.mock.calls + .map((c) => String(c[0])) + .filter((s) => s.includes("Dashboard bound")); expect(warnings).toEqual([]); }); @@ -49,15 +55,21 @@ describe("startDashboardServer host + token (#968)", () => { it("binds 0.0.0.0 when requested and prints a stderr warning", async () => { handle = await startDashboardServer(ctx(dir), { token: TOKEN, host: "0.0.0.0" }); expect(handle.url).toMatch(/^http:\/\/0\.0\.0\.0:\d+\/\?token=/); - const warnings = writeSpy.mock.calls.map((c) => String(c[0])).filter((s) => s.includes("▲")); + const warnings = writeSpy.mock.calls + .map((c) => String(c[0])) + .filter((s) => s.includes("Dashboard bound")); expect(warnings.length).toBe(1); + expect(warnings[0]).toMatch(/^WARNING:/); expect(warnings[0]).toContain("non-loopback"); expect(warnings[0]).toContain("token"); + expect(isAscii(warnings[0] ?? "")).toBe(true); }); it("does not warn for ::1 or localhost (still loopback)", async () => { handle = await startDashboardServer(ctx(dir), { token: TOKEN, host: "localhost" }); - const warnings = writeSpy.mock.calls.map((c) => String(c[0])).filter((s) => s.includes("▲")); + const warnings = writeSpy.mock.calls + .map((c) => String(c[0])) + .filter((s) => s.includes("Dashboard bound")); expect(warnings).toEqual([]); }); });