From 495a8fc95e86d6f3e587392cee23e88f46367e18 Mon Sep 17 00:00:00 2001 From: Patrick Ferdig <160992146+iceFusion101@users.noreply.github.com> Date: Sat, 12 Sep 2026 11:28:30 -0600 Subject: [PATCH 1/2] fix(codex): pass MCP bearer tokens through env Co-Authored-By: Paperclip --- .../codex-local/src/server/codex-home.test.ts | 7 ++++--- .../adapters/codex-local/src/server/codex-home.ts | 14 +++++++++++++- .../adapters/codex-local/src/server/execute.ts | 2 ++ server/src/__tests__/codex-local-execute.test.ts | 6 +++++- 4 files changed, 24 insertions(+), 5 deletions(-) diff --git a/packages/adapters/codex-local/src/server/codex-home.test.ts b/packages/adapters/codex-local/src/server/codex-home.test.ts index 5761d9756d..ea7fdd5a75 100644 --- a/packages/adapters/codex-local/src/server/codex-home.test.ts +++ b/packages/adapters/codex-local/src/server/codex-home.test.ts @@ -1023,7 +1023,8 @@ describe("evaluateCodexCredentialReadiness", () => { const alpha = await fs.readFile(path.join(alphaHome, "config.toml"), "utf8"); const zero = await fs.readFile(path.join(zeroHome, "config.toml"), "utf8"); expect(alpha).toContain('[mcp_servers."alpha"]'); - expect(alpha).toContain('Authorization = "Bearer alpha-token"'); + expect(alpha).toContain('bearer_token_env_var = "PAPERCLIP_MANAGED_MCP_BEARER_TOKEN_1"'); + expect(alpha).not.toContain("alpha-token"); expect(zero).not.toContain("mcp_servers."); expect(zero).not.toContain("stale-token"); expect(alphaHome).not.toBe(zeroHome); @@ -1142,7 +1143,7 @@ describe("stageCodexHomeForSync", () => { } }); - // config.toml carries the managed MCP `Authorization: Bearer …` header and is + // config.toml carries only the managed MCP bearer-token environment variable name and is // secret-bearing; the staged copy must be 0600, not the world-readable default. it("writes the staged config.toml (managed MCP bearer header) with mode 0600", async () => { const root = await fs.mkdtemp(path.join(os.tmpdir(), "paperclip-codex-stage-toml-mode-")); @@ -1154,7 +1155,7 @@ describe("stageCodexHomeForSync", () => { // and is persisted 0600 on disk. await fs.writeFile( path.join(home, "config.toml"), - "[mcp_servers.paperclip]\nheaders = { Authorization = \"Bearer secret-token\" }\n", + "[mcp_servers.paperclip]\nbearer_token_env_var = \"PAPERCLIP_MANAGED_MCP_BEARER_TOKEN_1\"\n", { mode: 0o600 }, ); staged = await stageCodexHomeForSync(home, { runId: "run-toml-mode" }); diff --git a/packages/adapters/codex-local/src/server/codex-home.ts b/packages/adapters/codex-local/src/server/codex-home.ts index b19a6d1d5d..655b8eb62c 100644 --- a/packages/adapters/codex-local/src/server/codex-home.ts +++ b/packages/adapters/codex-local/src/server/codex-home.ts @@ -10,6 +10,7 @@ const COPIED_SHARED_FILES = ["config.json", "config.toml", "instructions.md"] as const SYMLINKED_SHARED_FILES = ["auth.json"] as const; const MANAGED_MCP_BLOCK_START = "# BEGIN PAPERCLIP MANAGED MCP"; const MANAGED_MCP_BLOCK_END = "# END PAPERCLIP MANAGED MCP"; +const MANAGED_MCP_BEARER_TOKEN_ENV_PREFIX = "PAPERCLIP_MANAGED_MCP_BEARER_TOKEN_"; /** * The allowlist of managed `CODEX_HOME` entries that the codex-local adapter @@ -33,6 +34,17 @@ export type ManagedCodexMcpGateway = { bearerToken: string; }; +export function managedCodexMcpBearerTokenEnv( + gateways: ManagedCodexMcpGateway[], +): Record { + return Object.fromEntries( + gateways.map((gateway, index) => [ + `${MANAGED_MCP_BEARER_TOKEN_ENV_PREFIX}${index + 1}`, + gateway.bearerToken, + ]), + ); +} + export function mergeManagedCodexMcpGateways( primary: ManagedCodexMcpGateway[], secondary: ManagedCodexMcpGateway[], @@ -316,7 +328,7 @@ function buildManagedMcpBlock(input: { "", `[mcp_servers.${tomlString(managedName)}]`, `url = ${tomlString(url)}`, - `headers = { Authorization = ${tomlString(`Bearer ${gateway.bearerToken}`)} }`, + `bearer_token_env_var = ${tomlString(`${MANAGED_MCP_BEARER_TOKEN_ENV_PREFIX}${index + 1}`)}`, ); }); lines.push(MANAGED_MCP_BLOCK_END); diff --git a/packages/adapters/codex-local/src/server/execute.ts b/packages/adapters/codex-local/src/server/execute.ts index dc60fabad7..3642c5c0b7 100644 --- a/packages/adapters/codex-local/src/server/execute.ts +++ b/packages/adapters/codex-local/src/server/execute.ts @@ -78,6 +78,7 @@ import { resolveSharedCodexHomeDir, seedManagedCodexHome, stageCodexHomeForSync, + managedCodexMcpBearerTokenEnv, mergeManagedCodexMcpGateways, writeManagedCodexMcpConfig, type ManagedCodexMcpGateway, @@ -891,6 +892,7 @@ export async function execute(ctx: AdapterExecutionContext): Promise = { ...paperclipBaseEnv }; + Object.assign(env, managedCodexMcpBearerTokenEnv(managedMcpGateways)); env.PAPERCLIP_RUN_ID = runId; const wakeTaskId = (typeof context.taskId === "string" && context.taskId.trim().length > 0 && context.taskId.trim()) || diff --git a/server/src/__tests__/codex-local-execute.test.ts b/server/src/__tests__/codex-local-execute.test.ts index 22c3463266..e1d6c82a8e 100644 --- a/server/src/__tests__/codex-local-execute.test.ts +++ b/server/src/__tests__/codex-local-execute.test.ts @@ -23,6 +23,7 @@ const payload = { paperclipApiUrl: process.env.PAPERCLIP_API_URL || null, paperclipApiKey: process.env.PAPERCLIP_API_KEY || null, paperclipApiBridgeMode: process.env.PAPERCLIP_API_BRIDGE_MODE || null, + managedMcpBearerToken: process.env.PAPERCLIP_MANAGED_MCP_BEARER_TOKEN_1 || null, paperclipEnvKeys: Object.keys(process.env) .filter((key) => key.startsWith("PAPERCLIP_")) .sort(), @@ -56,6 +57,7 @@ type CapturePayload = { paperclipApiUrl?: string | null; paperclipApiKey?: string | null; paperclipApiBridgeMode?: string | null; + managedMcpBearerToken?: string | null; paperclipEnvKeys: string[]; }; @@ -313,7 +315,9 @@ describe("codex execute", () => { expect(configText).toContain("[mcp_servers.github]"); expect(configText).toContain("[mcp_servers.\"paperclip-github\"]"); expect(configText).toContain('url = "http://paperclip.local:3100/api/tool-gateway/gateways/gateway-1/mcp"'); - expect(configText).toContain('Authorization = "Bearer pcgw_secret-managed-token"'); + expect(configText).toContain('bearer_token_env_var = "PAPERCLIP_MANAGED_MCP_BEARER_TOKEN_1"'); + expect(configText).not.toContain("pcgw_secret-managed-token"); + expect(capture.managedMcpBearerToken).toBe("pcgw_secret-managed-token"); expect(logs).toEqual( expect.arrayContaining([ expect.objectContaining({ From 8e722c8254fd81a8742a996c8a001844f06a5a0f Mon Sep 17 00:00:00 2001 From: Patrick Ferdig <160992146+iceFusion101@users.noreply.github.com> Date: Sat, 12 Sep 2026 11:42:50 -0600 Subject: [PATCH 2/2] test(codex): clarify staged config mode rationale --- .../adapters/codex-local/src/server/codex-home.test.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/adapters/codex-local/src/server/codex-home.test.ts b/packages/adapters/codex-local/src/server/codex-home.test.ts index ea7fdd5a75..365ab4f4a0 100644 --- a/packages/adapters/codex-local/src/server/codex-home.test.ts +++ b/packages/adapters/codex-local/src/server/codex-home.test.ts @@ -1143,16 +1143,16 @@ describe("stageCodexHomeForSync", () => { } }); - // config.toml carries only the managed MCP bearer-token environment variable name and is - // secret-bearing; the staged copy must be 0600, not the world-readable default. - it("writes the staged config.toml (managed MCP bearer header) with mode 0600", async () => { + // Preserve the source config's restrictive mode when staging it, even though managed MCP + // bearer tokens are now referenced by environment-variable name instead of stored here. + it("writes the staged config.toml with mode 0600", async () => { const root = await fs.mkdtemp(path.join(os.tmpdir(), "paperclip-codex-stage-toml-mode-")); let staged: string | null = null; try { const home = path.join(root, "codex-home"); await fs.mkdir(home, { recursive: true }); - // Mirror the source writer: config.toml holds an MCP gateway bearer token - // and is persisted 0600 on disk. + // Mirror the source writer: config.toml names the environment variable that supplies + // the MCP gateway bearer token and is persisted 0600 on disk. await fs.writeFile( path.join(home, "config.toml"), "[mcp_servers.paperclip]\nbearer_token_env_var = \"PAPERCLIP_MANAGED_MCP_BEARER_TOKEN_1\"\n",