From fab31a2baea02f24f0e61785f51a0d6d5060d337 Mon Sep 17 00:00:00 2001 From: usr_bin_roygbiv Date: Thu, 16 Jul 2026 00:24:00 -0500 Subject: [PATCH] fix(ai): redact sensitive credentials in system prompt instructions --- packages/ai/src/utils.ts | 5 ++++- .../ai/test/openai-responses-system-prompt.test.ts | 10 ++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/ai/src/utils.ts b/packages/ai/src/utils.ts index 0445cd3ac..5c8a4bf9d 100644 --- a/packages/ai/src/utils.ts +++ b/packages/ai/src/utils.ts @@ -1,5 +1,6 @@ import { $env } from "@oh-my-pi/pi-utils"; import type { ResponseInput, ResponseInputItem } from "./providers/openai-responses-wire"; +import { redactSensitiveCredentials } from "./providers/transform-messages"; import type { CacheRetention, OpenAIResponsesHistoryPayload, ProviderPayload } from "./types"; type OpenAIResponsesReplayItem = ResponseInput[number]; @@ -9,7 +10,9 @@ export { isRecord } from "@oh-my-pi/pi-utils"; export function normalizeSystemPrompts(systemPrompt: readonly string[] | string | undefined | null): string[] { if (systemPrompt === undefined || systemPrompt === null) return []; const prompts = Array.isArray(systemPrompt) ? systemPrompt : typeof systemPrompt === "string" ? [systemPrompt] : []; - return prompts.map(prompt => prompt.toWellFormed()).filter(prompt => prompt.trim().length > 0); + return prompts + .map(prompt => redactSensitiveCredentials(prompt.toWellFormed())) + .filter(prompt => prompt.trim().length > 0); } export function normalizeToolCallId(id: string): string { diff --git a/packages/ai/test/openai-responses-system-prompt.test.ts b/packages/ai/test/openai-responses-system-prompt.test.ts index f09888189..26103a45c 100644 --- a/packages/ai/test/openai-responses-system-prompt.test.ts +++ b/packages/ai/test/openai-responses-system-prompt.test.ts @@ -86,6 +86,16 @@ describe("openai-responses system prompt routing", () => { expect(input.every(m => m.role !== "system")).toBe(true); }); + it("redacts sensitive credentials in instructions", async () => { + const context: Context = { + systemPrompt: ["Token: gho_************************************"], + messages: [{ role: "user", content: "hi", timestamp: Date.now() }], + }; + const body = await captureRequestBody(gpt4oMiniModel, context); + + expect(body.instructions).toBe("Token: [github_token_redacted]"); + }); + it("omits instructions field when there is no system prompt", async () => { const context: Context = { systemPrompt: undefined,