diff --git a/packages/web-content-core/src/provider-runtime-shared.test.ts b/packages/web-content-core/src/provider-runtime-shared.test.ts index b10ddd08ccf6..c57d5a0aa7b0 100644 --- a/packages/web-content-core/src/provider-runtime-shared.test.ts +++ b/packages/web-content-core/src/provider-runtime-shared.test.ts @@ -30,6 +30,15 @@ describe("readWebProviderEnvValue", () => { it("normalizes env credentials before returning them", () => { expect(readWebProviderEnvValue(["API_KEY"], { API_KEY: " key\r\nvalue🙂 " })).toBe("keyvalue"); }); + + it("strips embedded controls from env credentials while preserving ordinary spaces", () => { + expect(readWebProviderEnvValue(["API_KEY"], { API_KEY: " sk-\u0000ab\tc\u007f\u0085 " })).toBe( + "sk-abc", + ); + expect(readWebProviderEnvValue(["API_KEY"], { API_KEY: " Bearer token value " })).toBe( + "Bearer token value", + ); + }); }); describe("hasWebProviderEntryCredential", () => { diff --git a/packages/web-content-core/src/provider-runtime-shared.ts b/packages/web-content-core/src/provider-runtime-shared.ts index 723d1d2d8a72..450821321ec2 100644 --- a/packages/web-content-core/src/provider-runtime-shared.ts +++ b/packages/web-content-core/src/provider-runtime-shared.ts @@ -56,7 +56,12 @@ function normalizeSecretInput(value: unknown): string { let latin1Only = ""; for (const char of collapsed) { const codePoint = char.codePointAt(0); - if (typeof codePoint === "number" && codePoint <= 0xff) { + const isControl = + typeof codePoint === "number" && + ((codePoint >= 0x00 && codePoint <= 0x1f) || + codePoint === 0x7f || + (codePoint >= 0x80 && codePoint <= 0x9f)); + if (typeof codePoint === "number" && codePoint <= 0xff && !isControl) { latin1Only += char; } } diff --git a/src/utils/normalize-secret-input.test.ts b/src/utils/normalize-secret-input.test.ts index 721f6d7ed98f..232c0a2954e6 100644 --- a/src/utils/normalize-secret-input.test.ts +++ b/src/utils/normalize-secret-input.test.ts @@ -14,6 +14,11 @@ describe("normalizeSecretInput", () => { expect(normalizeSecretInput(" sk-\r\nabc\n123 ")).toBe("sk-abc123"); }); + it("strips embedded control characters while preserving ordinary spaces", () => { + expect(normalizeSecretInput(" sk-\u0000ab\tc\u007f\u0085 ")).toBe("sk-abc"); + expect(normalizeSecretInput(" Bearer token value ")).toBe("Bearer token value"); + }); + it("drops non-Latin1 code points that can break HTTP ByteString headers", () => { // U+0417 (Cyrillic З) and U+2502 (box drawing │) are > 255. expect(normalizeSecretInput("key-\u0417\u2502-token")).toBe("key--token"); diff --git a/src/utils/normalize-secret-input.ts b/src/utils/normalize-secret-input.ts index 47b79b76b0df..ab5840f938dd 100644 --- a/src/utils/normalize-secret-input.ts +++ b/src/utils/normalize-secret-input.ts @@ -25,7 +25,12 @@ export function normalizeSecretInput(value: unknown): string { const chars: string[] = []; for (const char of collapsed) { const codePoint = char.codePointAt(0); - if (typeof codePoint === "number" && codePoint <= 0xff) { + const isControl = + typeof codePoint === "number" && + ((codePoint >= 0x00 && codePoint <= 0x1f) || + codePoint === 0x7f || + (codePoint >= 0x80 && codePoint <= 0x9f)); + if (typeof codePoint === "number" && codePoint <= 0xff && !isControl) { chars.push(char); } }