From 59e3fb40ba932ce38bd566dc6daf53fde9c2853e Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 14 Sep 2026 18:02:16 +0000 Subject: [PATCH] fix(mcp): classify auth-validation failures before logging Split the validateApiKey and validateOAuthToken catch blocks so transient auth-backend failures log at ERROR under a distinct 'Auth backend transient failure:' template while expected rejections log at debug. Return values and 401/503 status contract are unchanged. Co-authored-by: Dhravya Shah --- apps/mcp/src/server/auth/index.test.ts | 26 ++++++++++++++++++++------ apps/mcp/src/server/auth/index.ts | 14 ++++++++++---- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/apps/mcp/src/server/auth/index.test.ts b/apps/mcp/src/server/auth/index.test.ts index d551a7e2..ee85bc64 100644 --- a/apps/mcp/src/server/auth/index.test.ts +++ b/apps/mcp/src/server/auth/index.test.ts @@ -83,16 +83,19 @@ describe("MCP authentication", () => { }) it("rejects a token issued for a different audience", async () => { - vi.spyOn(console, "error").mockImplementation(() => {}) + const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {}) + vi.spyOn(console, "debug").mockImplementation(() => {}) const token = await signToken({ audience: "https://api.example.com" }) await expect( validateOAuthToken(token, API_URL, MCP_RESOURCE, keySet), ).resolves.toBeNull() + expect(errorSpy).not.toHaveBeenCalled() }) it("rejects expired tokens and tokens without a subject", async () => { - vi.spyOn(console, "error").mockImplementation(() => {}) + const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {}) + vi.spyOn(console, "debug").mockImplementation(() => {}) const expired = await signToken({ expiresIn: "-1s" }) const noSubject = await signToken({ subject: "" }) @@ -102,10 +105,11 @@ describe("MCP authentication", () => { await expect( validateOAuthToken(noSubject, API_URL, MCP_RESOURCE, keySet), ).resolves.toBeNull() + expect(errorSpy).not.toHaveBeenCalled() }) it("rejects opaque API keys without an API request", async () => { - vi.spyOn(console, "error").mockImplementation(() => {}) + vi.spyOn(console, "debug").mockImplementation(() => {}) const fetchSpy = vi.fn() vi.stubGlobal("fetch", fetchSpy) @@ -166,7 +170,8 @@ describe("MCP authentication", () => { }) it("rejects an API key the session endpoint refuses", async () => { - vi.spyOn(console, "error").mockImplementation(() => {}) + const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {}) + vi.spyOn(console, "debug").mockImplementation(() => {}) vi.stubGlobal( "fetch", vi.fn().mockResolvedValue(new Response(null, { status: 401 })), @@ -175,6 +180,7 @@ describe("MCP authentication", () => { await expect( validateApiKey("sm_revoked_key_0123456789abcdef", API_URL), ).resolves.toBeNull() + expect(errorSpy).not.toHaveBeenCalled() }) it("rejects malformed API keys without an API request", async () => { @@ -187,7 +193,7 @@ describe("MCP authentication", () => { }) it("surfaces a 500 from the session endpoint as TransientAuthError, not invalid token", async () => { - vi.spyOn(console, "error").mockImplementation(() => {}) + const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {}) vi.stubGlobal( "fetch", vi.fn().mockResolvedValue(new Response(null, { status: 500 })), @@ -196,10 +202,14 @@ describe("MCP authentication", () => { await expect( validateApiKey("sm_outage_key_0123456789abcdef", API_URL), ).rejects.toThrow(TransientAuthError) + expect(errorSpy).toHaveBeenCalledWith( + "Auth backend transient failure:", + expect.anything(), + ) }) it("surfaces a session-endpoint timeout as TransientAuthError", async () => { - vi.spyOn(console, "error").mockImplementation(() => {}) + const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {}) vi.stubGlobal( "fetch", vi.fn().mockRejectedValue( @@ -212,5 +222,9 @@ describe("MCP authentication", () => { await expect( validateApiKey("sm_timeout_key_0123456789abcd", API_URL), ).rejects.toThrow(TransientAuthError) + expect(errorSpy).toHaveBeenCalledWith( + "Auth backend transient failure:", + expect.anything(), + ) }) }) diff --git a/apps/mcp/src/server/auth/index.ts b/apps/mcp/src/server/auth/index.ts index 3e2374a9..0ecdf8f3 100644 --- a/apps/mcp/src/server/auth/index.ts +++ b/apps/mcp/src/server/auth/index.ts @@ -132,9 +132,12 @@ export async function validateApiKey( }) return user } catch (error) { - console.error("API key validation error:", error) const transient = transientAuthErrorFor(error) - if (transient) throw transient + if (transient) { + console.error("Auth backend transient failure:", error) + throw transient + } + console.debug("API key validation rejected:", error) return null } } @@ -183,9 +186,12 @@ export async function validateOAuthToken( expiresAt: payload.exp, } } catch (error) { - console.error("OAuth token validation error:", error) const transient = transientAuthErrorFor(error) - if (transient) throw transient + if (transient) { + console.error("Auth backend transient failure:", error) + throw transient + } + console.debug("OAuth token validation rejected:", error) return null } }