From d3da6b8e271db721dd3f3bdf67142df624c39baf Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Wed, 24 Jun 2026 16:58:40 -0500 Subject: [PATCH] fix(opencode): redact provider model secrets --- packages/opencode/src/config/config.ts | 19 +++++++++++++ .../test/server/httpapi-config.test.ts | 27 ++++++++++++++++--- 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 400969eab2..ab97020f78 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -128,6 +128,22 @@ export function toPublicInfo(info: Info): Info { options: provider.options ? (redactProviderOptions(provider.options) as typeof provider.options) : undefined, + models: provider.models + ? Object.fromEntries( + Object.entries(provider.models).map(([id, model]) => [ + id, + { + ...model, + options: model.options + ? (redactProviderOptions(model.options) as typeof model.options) + : undefined, + headers: model.headers + ? Object.fromEntries(Object.keys(model.headers).map((key) => [key, redacted])) + : undefined, + }, + ]), + ) + : undefined, }, ]), ) @@ -168,6 +184,9 @@ export function toPublicInfo(info: Info): Info { function redactProviderOptions(value: unknown, key?: string): unknown { const normalized = key?.replaceAll(/[-_]/g, "").toLowerCase() + if (normalized === "headers" && isRecord(value)) { + return Object.fromEntries(Object.keys(value).map((key) => [key, redacted])) + } if ( normalized && (normalized.endsWith("apikey") || diff --git a/packages/opencode/test/server/httpapi-config.test.ts b/packages/opencode/test/server/httpapi-config.test.ts index 5b80fd8972..e8fff977e8 100644 --- a/packages/opencode/test/server/httpapi-config.test.ts +++ b/packages/opencode/test/server/httpapi-config.test.ts @@ -71,6 +71,9 @@ describe("config HttpApi", () => { "CANARY_PROVIDER_API_KEY", "CANARY_PROVIDER_CLIENT_SECRET", "CANARY_PROVIDER_NESTED_TOKEN", + "CANARY_PROVIDER_HEADER", + "CANARY_MODEL_API_KEY", + "CANARY_MODEL_HEADER", "CANARY_MCP_ENV", "CANARY_MCP_HEADER", "CANARY_MCP_CLIENT_SECRET", @@ -88,22 +91,31 @@ describe("config HttpApi", () => { apiKey: "{file:secret-0}", clientSecret: "{file:secret-1}", nested: { accessToken: "{file:secret-2}", temperature: 0.5 }, + headers: { "x-custom-auth": "{file:secret-3}" }, baseURL: "https://provider.example.com", }, + models: { + canary: { + name: "Canary Model", + status: "active", + options: { apiKey: "{file:secret-4}", temperature: 0.7 }, + headers: { "x-custom-auth": "{file:secret-5}" }, + }, + }, }, }, mcp: { local: { type: "local", command: ["canary-command"], - environment: { TOKEN: "{file:secret-3}" }, + environment: { TOKEN: "{file:secret-6}" }, enabled: false, }, remote: { type: "remote", url: "https://mcp.example.com", - headers: { Authorization: "{file:secret-4}" }, - oauth: { clientId: "canary-client", clientSecret: "{file:secret-5}", scope: "read" }, + headers: { Authorization: "{file:secret-7}" }, + oauth: { clientId: "canary-client", clientSecret: "{file:secret-8}", scope: "read" }, enabled: false, }, }, @@ -132,8 +144,17 @@ describe("config HttpApi", () => { apiKey: "[redacted]", clientSecret: "[redacted]", nested: { accessToken: "[redacted]", temperature: 0.5 }, + headers: { "x-custom-auth": "[redacted]" }, baseURL: "https://provider.example.com", }, + models: { + canary: { + name: "Canary Model", + status: "active", + options: { apiKey: "[redacted]", temperature: 0.7 }, + headers: { "x-custom-auth": "[redacted]" }, + }, + }, }, }, mcp: {