From 8b55a10c02f749615df5e5fc7941fddbaf92e983 Mon Sep 17 00:00:00 2001 From: Dave Date: Sat, 26 Sep 2026 10:23:07 +0200 Subject: [PATCH] fix(agent-api): reject placeholder/weak AGENT_API_TOKEN as unconfigured .env.example ships AGENT_API_TOKEN=replace-me, and unlike PB_SUPERUSER_PASSWORD's own replace-me placeholder, an unchanged agent token fails open: any caller sending "Bearer replace-me" would get full owner-equivalent access. Treat a secret that is unset, literally "replace-me", or shorter than 32 characters as not configured, matching the existing 500 behavior for an unset token. Co-Authored-By: Claude Sonnet 5 --- src/lib/agent/auth.test.ts | 32 ++++++++++++++++++++++++++++---- src/lib/agent/auth.ts | 6 ++++-- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/src/lib/agent/auth.test.ts b/src/lib/agent/auth.test.ts index ab216d8..42a9f27 100644 --- a/src/lib/agent/auth.test.ts +++ b/src/lib/agent/auth.test.ts @@ -20,7 +20,7 @@ describe("requireAgentToken", () => { }); it("refuses a request with no Authorization header", () => { - process.env.AGENT_API_TOKEN = "secret123"; + process.env.AGENT_API_TOKEN = "secret123secret123secret123secret123"; const request = new Request("http://localhost/api/agent/projects"); const result = requireAgentToken(request); @@ -30,7 +30,7 @@ describe("requireAgentToken", () => { }); it("refuses a request with the wrong token", () => { - process.env.AGENT_API_TOKEN = "secret123"; + process.env.AGENT_API_TOKEN = "secret123secret123secret123secret123"; const request = new Request("http://localhost/api/agent/projects", { headers: { authorization: "Bearer wrong" }, }); @@ -42,13 +42,37 @@ describe("requireAgentToken", () => { }); it("allows a request with the exact right token", () => { - process.env.AGENT_API_TOKEN = "secret123"; + process.env.AGENT_API_TOKEN = "secret123secret123secret123secret123"; const request = new Request("http://localhost/api/agent/projects", { - headers: { authorization: "Bearer secret123" }, + headers: { authorization: "Bearer secret123secret123secret123secret123" }, }); const result = requireAgentToken(request); expect(result).toBeNull(); }); + + it("refuses with 500 when the token is the literal placeholder, even with a matching header", () => { + process.env.AGENT_API_TOKEN = "replace-me"; + const request = new Request("http://localhost/api/agent/projects", { + headers: { authorization: "Bearer replace-me" }, + }); + + const result = requireAgentToken(request); + + expect(result).not.toBeNull(); + expect(result!.status).toBe(500); + }); + + it("refuses with 500 when the token is shorter than 32 characters, even with a matching header", () => { + process.env.AGENT_API_TOKEN = "short"; + const request = new Request("http://localhost/api/agent/projects", { + headers: { authorization: "Bearer short" }, + }); + + const result = requireAgentToken(request); + + expect(result).not.toBeNull(); + expect(result!.status).toBe(500); + }); }); diff --git a/src/lib/agent/auth.ts b/src/lib/agent/auth.ts index db9cec6..7d96502 100644 --- a/src/lib/agent/auth.ts +++ b/src/lib/agent/auth.ts @@ -15,8 +15,10 @@ import { jsonError } from "@/lib/http/json"; */ export function requireAgentToken(request: Request): Response | null { const secret = process.env.AGENT_API_TOKEN; - if (!secret) { - console.error("[agent-api] AGENT_API_TOKEN is not set; refusing all requests"); + if (!secret || secret === "replace-me" || secret.length < 32) { + console.error( + "[agent-api] AGENT_API_TOKEN is not set to a real secret; refusing all requests" + ); return jsonError("Not configured", 500); }