From 859d74f8267b87fdc884c20253a1f0d391135e2a Mon Sep 17 00:00:00 2001 From: Sebastion Date: Wed, 29 Apr 2026 12:38:36 +0100 Subject: [PATCH 1/2] fix: sanitize Perplexity API errors --- src/index.test.ts | 13 +++++++------ src/server.ts | 17 +++++++++++++---- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/index.test.ts b/src/index.test.ts index 21897b8..6c71182 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -418,20 +418,21 @@ describe("Perplexity MCP Server", () => { ); }); - it("should handle error text parse failures", async () => { + it("should not expose upstream error response details", async () => { global.fetch = vi.fn().mockResolvedValue({ ok: false, status: 500, statusText: "Internal Server Error", - text: async () => { - throw new Error("Cannot read error"); - }, - } as unknown as Response); + text: async () => "internal_trace=abc123; account=private-tier", + } as Response); const messages = [{ role: "user", content: "test" }]; await expect(performChatCompletion(messages)).rejects.toThrow( - "Unable to parse error response" + "Perplexity API error: 500 Internal Server Error" + ); + await expect(performChatCompletion(messages)).rejects.not.toThrow( + "internal_trace=abc123" ); }); diff --git a/src/server.ts b/src/server.ts index d649ccb..368b6b3 100644 --- a/src/server.ts +++ b/src/server.ts @@ -10,6 +10,7 @@ import type { UndiciRequestOptions } from "./types.js"; import { ChatCompletionResponseSchema, SearchResponseSchema } from "./validation.js"; +import { logger } from "./logger.js"; const PERPLEXITY_API_KEY = process.env.PERPLEXITY_API_KEY; const PERPLEXITY_BASE_URL = process.env.PERPLEXITY_BASE_URL || "https://api.perplexity.ai"; @@ -99,7 +100,8 @@ async function makeApiRequest( if (error instanceof Error && error.name === "AbortError") { throw new Error(`Request timeout: Perplexity API did not respond within ${TIMEOUT_MS}ms. Consider increasing PERPLEXITY_TIMEOUT_MS.`); } - throw new Error(`Network error while calling Perplexity API: ${error}`); + logger.error("Network error while calling Perplexity API", { error: String(error) }); + throw new Error("Network error while calling Perplexity API"); } clearTimeout(timeoutId); @@ -110,8 +112,13 @@ async function makeApiRequest( } catch (parseError) { errorText = "Unable to parse error response"; } + logger.error("Perplexity API error", { + status: response.status, + statusText: response.statusText, + body: errorText, + }); throw new Error( - `Perplexity API error: ${response.status} ${response.statusText}\n${errorText}` + `Perplexity API error: ${response.status} ${response.statusText}` ); } @@ -228,7 +235,8 @@ export async function performChatCompletion( throw new Error("Invalid API response: missing or empty choices array"); } } - throw new Error(`Failed to parse JSON response from Perplexity API: ${error}`); + logger.error("Failed to parse JSON response from Perplexity API", { error: String(error) }); + throw new Error("Failed to parse JSON response from Perplexity API"); } const firstChoice = data.choices[0]; @@ -292,7 +300,8 @@ export async function performSearch( const json = await response.json(); data = SearchResponseSchema.parse(json); } catch (error) { - throw new Error(`Failed to parse JSON response from Perplexity Search API: ${error}`); + logger.error("Failed to parse JSON response from Perplexity Search API", { error: String(error) }); + throw new Error("Failed to parse JSON response from Perplexity Search API"); } return formatSearchResults(data); From ff52310c1db083d995819feb1f413a5a15811c8a Mon Sep 17 00:00:00 2001 From: Sebastion Date: Wed, 29 Apr 2026 19:02:11 +0100 Subject: [PATCH 2/2] test: cover sanitized Perplexity error messages --- src/index.test.ts | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/index.test.ts b/src/index.test.ts index 6c71182..9cd19b4 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -418,6 +418,39 @@ describe("Perplexity MCP Server", () => { ); }); + it("should not expose JSON parse error details", async () => { + global.fetch = vi.fn().mockResolvedValue({ + ok: true, + json: async () => { + throw new Error("Invalid JSON containing secret_token=abc123"); + }, + } as unknown as Response); + + const messages = [{ role: "user", content: "test" }]; + + await expect(performChatCompletion(messages)).rejects.toThrow( + "Failed to parse JSON response from Perplexity API" + ); + await expect(performChatCompletion(messages)).rejects.not.toThrow( + "secret_token=abc123" + ); + }); + + it("should not expose network error details", async () => { + global.fetch = vi.fn().mockRejectedValue( + new Error("Network failure with credential=private-token") + ); + + const messages = [{ role: "user", content: "test" }]; + + await expect(performChatCompletion(messages)).rejects.toThrow( + "Network error while calling Perplexity API" + ); + await expect(performChatCompletion(messages)).rejects.not.toThrow( + "credential=private-token" + ); + }); + it("should not expose upstream error response details", async () => { global.fetch = vi.fn().mockResolvedValue({ ok: false,