Redact custom provider credentials from OpenCode diagnostics

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-10-02 17:47:32 -05:00
1 parent 7292c79988
commit 8bded58b3e
2 files changed
+47 -12

No files matched your search

@@ -2262,16 +2262,20 @@ describe("OpenCodeServerDriver", () => {
const exitingFixture = join(root, "exit-before-health.mjs");
await writeFile(
exitingFixture,
"#!/usr/bin/env node\nprocess.stderr.write(`credential=${process.env.OPENROUTER_API_KEY}\\nauthorization=super-secret-opencode-token\\n`);\nprocess.exit(17);\n",
"#!/usr/bin/env node\nimport { readFileSync } from 'node:fs';\nconst config = JSON.parse(readFileSync(`${process.env.XDG_CONFIG_HOME}/opencode/opencode.json`, 'utf8'));\nprocess.stderr.write(`credential=${process.env.OPENROUTER_API_KEY}\\ngateway=${config.provider.paperclip.options.apiKey}\\nauthorization=super-secret-opencode-token\\n`);\nprocess.exit(17);\n",
{ mode: 0o755 },
);
const diagnostics: string[] = [];
const driver = new OpenCodeServerDriver({
model: "openrouter/deepseek/deepseek-v4-flash-0731",
model: "paperclip/team/model-alias",
runtimeDirectory: root,
command: exitingFixture,
onDiagnostic: (message) => { diagnostics.push(message); },
environment: {
PATH: process.env.PATH,
OPENROUTER_API_KEY: "fixture-key",
PAPERCLIP_AI_PROVIDER_KEY: "fixture-custom-gateway-key",
PAPERCLIP_AI_PROVIDER_URL: "https://gateway.example/v1",
},
});
const error = await driver
@@ -2289,6 +2293,41 @@ describe("OpenCodeServerDriver", () => {
expect(error).toContain("stage=health");
expect(error).toContain("[REDACTED]");
expect(error).not.toContain("fixture-key");
expect(error).not.toContain("fixture-custom-gateway-key");
expect(error).not.toContain("super-secret-opencode-token");
expect(diagnostics.join("")).toContain("gateway=[REDACTED]");
expect(diagnostics.join("")).not.toContain("fixture-custom-gateway-key");
});
it.each(["network", "response"])("redacts custom gateway keys in %s errors", async (failure) => {
await chmod(fixture, 0o755);
const root = await mkdtemp(join(tmpdir(), "paperclip-opencode-driver-"));
const workspace = await mkdtemp(join(tmpdir(), "paperclip-opencode-workspace-"));
roots.push(root, workspace);
const key = "fixture-custom-gateway-key";
const driver = new OpenCodeServerDriver({
model: "paperclip/team/model-alias",
runtimeDirectory: root,
command: fixture,
environment: {
PATH: process.env.PATH,
PAPERCLIP_AI_PROVIDER_KEY: key,
PAPERCLIP_AI_PROVIDER_URL: "https://gateway.example/v1",
},
fetch: async (input, init) => {
if (String(input).endsWith("/session") && init?.method === "POST") {
if (failure === "network") throw new Error(`Gateway rejected ${key}`);
return new Response(`Gateway rejected ${key}`, { status: 400 });
}
return fetch(input, init);
},
});
const error = await driver.openSession({
runId: "run-gateway-error",
normalizedSessionId: "gateway-error",
workingDirectory: workspace,
}).then(() => "provider unexpectedly started", (cause: unknown) => String(cause));
expect(error).toContain("Gateway rejected [REDACTED]");
expect(error).not.toContain(key);
});
});
@@ -254,6 +254,7 @@ export class OpenCodeServerDriver implements HarnessDriver {
recovered: false,
reason: redact(String(error), [
this.#options.environment?.OPENROUTER_API_KEY,
this.#options.environment?.PAPERCLIP_AI_PROVIDER_KEY,
]),
};
}
@@ -1970,14 +1971,15 @@ async function startRuntime(input: {
const assignedMcp = nativeMcpLaunchBinding(
input.options.environment ?? process.env,
);
input.trace?.addSensitiveValues([
const sensitiveValues = [
password,
authHeader,
bridge.secret,
assignedMcp?.token,
input.options.environment?.OPENROUTER_API_KEY,
input.options.environment?.PAPERCLIP_AI_PROVIDER_KEY,
]);
].filter((value): value is string => Boolean(value));
input.trace?.addSensitiveValues(sensitiveValues);
const instructionRoot =
input.options.runtimeContext?.instructions.bundle.rootPath;
const [modelProvider, ...modelIdParts] = input.options.model.split("/");
@@ -2089,10 +2091,7 @@ async function startRuntime(input: {
let diagnostics = "";
child.stderr?.on("data", (chunk) => {
const raw = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk));
const redactedDiagnostic = redact(raw.toString("utf8"), [
password,
input.options.environment?.OPENROUTER_API_KEY,
]);
const redactedDiagnostic = redact(raw.toString("utf8"), sensitiveValues);
diagnostics = `${diagnostics}${redactedDiagnostic}`.slice(-8_192);
const frameId = input.trace?.frame({
direction: "provider_stderr",
@@ -2155,10 +2154,7 @@ async function startRuntime(input: {
process: child,
bridge,
trace: input.trace,
sensitiveValues: [
password,
input.options.environment?.OPENROUTER_API_KEY,
].filter((value): value is string => Boolean(value)),
sensitiveValues,
close: async (closeInput = {}) => {
await bridge.close().catch(() => {});
if (child.exitCode === null && child.signalCode === null && child.pid) {