From 3b2705957b52f7b9049aad5cb993f1caeea0ee09 Mon Sep 17 00:00:00 2001 From: James Hume Date: Thu, 11 Jun 2026 00:02:27 -0400 Subject: [PATCH] feat(agent-memory): accept dedicated AGENT_MEMORY_ACCESS_KEY with shared-key fallback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Prefer the dedicated read-surface key when set; retain the shared MCP_ACCESS_KEY fallback until Stone-side harnesses migrate (design decision D1 — full decouple is a later increment). Matches agent-memory-api v6 deployed 2026-06-11. Co-Authored-By: Claude Fable 5 --- .../agent-memory-api/dual-key-auth.test.ts | 56 +++++++++++++++++++ integrations/agent-memory-api/index.ts | 14 +++++ 2 files changed, 70 insertions(+) create mode 100644 integrations/agent-memory-api/dual-key-auth.test.ts diff --git a/integrations/agent-memory-api/dual-key-auth.test.ts b/integrations/agent-memory-api/dual-key-auth.test.ts new file mode 100644 index 000000000..6ee2982d4 --- /dev/null +++ b/integrations/agent-memory-api/dual-key-auth.test.ts @@ -0,0 +1,56 @@ +import { assertEquals } from "jsr:@std/assert@1"; +import { app, configureAgentMemoryAppForTest } from "./index.ts"; + +const NEW_KEY = "test-dedicated-agent-memory-key"; +const OLD_KEY = "test-shared-mcp-key"; + +async function healthStatus(key?: string): Promise { + const headers: Record = {}; + if (key) headers["x-brain-key"] = key; + const res = await app.fetch( + new Request("http://localhost/health", { headers }), + ); + await res.body?.cancel(); + return res.status; +} + +Deno.test("dual-key: new dedicated key accepted when set", async () => { + configureAgentMemoryAppForTest({ + mcpAccessKey: OLD_KEY, + agentMemoryAccessKey: NEW_KEY, + }); + assertEquals(await healthStatus(NEW_KEY), 200); +}); + +Deno.test("dual-key: MCP_ACCESS_KEY fallback still accepted (D1 interim)", async () => { + configureAgentMemoryAppForTest({ + mcpAccessKey: OLD_KEY, + agentMemoryAccessKey: NEW_KEY, + }); + assertEquals(await healthStatus(OLD_KEY), 200); +}); + +Deno.test("dual-key: wrong key rejected 401", async () => { + configureAgentMemoryAppForTest({ + mcpAccessKey: OLD_KEY, + agentMemoryAccessKey: NEW_KEY, + }); + assertEquals(await healthStatus("wrong-key"), 401); +}); + +Deno.test("dual-key: missing key rejected 401", async () => { + configureAgentMemoryAppForTest({ + mcpAccessKey: OLD_KEY, + agentMemoryAccessKey: NEW_KEY, + }); + assertEquals(await healthStatus(undefined), 401); +}); + +Deno.test("dual-key: unset dedicated key preserves MCP-only behavior", async () => { + configureAgentMemoryAppForTest({ + mcpAccessKey: OLD_KEY, + agentMemoryAccessKey: "", + }); + assertEquals(await healthStatus(OLD_KEY), 200); + assertEquals(await healthStatus(NEW_KEY), 401); +}); diff --git a/integrations/agent-memory-api/index.ts b/integrations/agent-memory-api/index.ts index 8d2714a11..26f564e29 100644 --- a/integrations/agent-memory-api/index.ts +++ b/integrations/agent-memory-api/index.ts @@ -29,6 +29,7 @@ const SUPABASE_URL = safeEnv("SUPABASE_URL")!; const SUPABASE_SERVICE_ROLE_KEY = safeEnv("SUPABASE_SERVICE_ROLE_KEY")!; const OPENROUTER_API_KEY = safeEnv("OPENROUTER_API_KEY")!; let MCP_ACCESS_KEY = safeEnv("MCP_ACCESS_KEY")!; +let AGENT_MEMORY_ACCESS_KEY = safeEnv("AGENT_MEMORY_ACCESS_KEY"); const OPENROUTER_BASE = "https://openrouter.ai/api/v1"; let AGENT_MEMORY_READ_ONLY = parseBooleanEnv(safeEnv("AGENT_MEMORY_READ_ONLY")); let AGENT_MEMORY_ALLOW_QUERY_KEY = parseBooleanEnv( @@ -57,6 +58,7 @@ function db() { type AgentMemoryAppTestRuntime = { supabase?: SupabaseClientLike; mcpAccessKey?: string; + agentMemoryAccessKey?: string; readOnly?: boolean; allowQueryKey?: boolean; allowedScope?: { @@ -70,6 +72,9 @@ export function configureAgentMemoryAppForTest( ) { if (runtime.supabase) supabase = runtime.supabase; if (runtime.mcpAccessKey !== undefined) MCP_ACCESS_KEY = runtime.mcpAccessKey; + if (runtime.agentMemoryAccessKey !== undefined) { + AGENT_MEMORY_ACCESS_KEY = runtime.agentMemoryAccessKey; + } if (runtime.readOnly !== undefined) AGENT_MEMORY_READ_ONLY = runtime.readOnly; if (runtime.allowQueryKey !== undefined) { AGENT_MEMORY_ALLOW_QUERY_KEY = runtime.allowQueryKey; @@ -301,6 +306,15 @@ function auth(c: { req: { raw: Request; url: string } }) { const provided = selectAccessKey(c.req.raw.headers, c.req.url, { allowQueryKey: AGENT_MEMORY_ALLOW_QUERY_KEY, }); + // Dedicated read-surface key is preferred when set; the shared + // MCP_ACCESS_KEY fallback is retained until Stone-side harnesses + // migrate (design decision D1 — full decouple is a later increment). + if ( + AGENT_MEMORY_ACCESS_KEY && + accessKeyMatches(provided, AGENT_MEMORY_ACCESS_KEY) + ) { + return true; + } return accessKeyMatches(provided, MCP_ACCESS_KEY); }