authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-10-08 22:52:42-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-10-08 22:52:42-07:00
logc908cea52510ed29199c3cc477204da425601301
tree7811c27148721d755a8c52dc235c4cf7d7ff5189
parent3b6af0bd1600f034b0466e1b8ff017fbabeba910
signature Signed by PGP key B5690EEEBB952194

fix(server): keep the Claude MCP token out of process arguments (#17408)


2 files changed, 35 insertions(+), 6 deletions(-)

apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts+16-3
......@@ -478,11 +478,14 @@ describe("ClaudeAdapterV2 MCP query overrides", () => {
478478 type: "http",
479479 url: "http://127.0.0.1:43123/mcp",
480480 headers: {
481 Authorization: "Bearer secret-claude-token",
481 Authorization: "${T3_CODE_MCP_AUTHORIZATION}",
482482 },
483483 timeout: ClaudeAdapterV2.CLAUDE_T3_MCP_TOOL_TIMEOUT_MS,
484484 },
485485 } as const;
486 const T3_MCP_ENVIRONMENT = {
487 T3_CODE_MCP_AUTHORIZATION: "Bearer secret-claude-token",
488 } as const;
486489
487490 const withMcpSession = (threadId: ThreadId, run: () => void) => {
488491 McpProviderSession.setMcpProviderSession({
......@@ -531,6 +534,7 @@ describe("ClaudeAdapterV2 MCP query overrides", () => {
531534 assert.deepEqual(overrides, {
532535 allowedTools: [ClaudeAdapterV2.CLAUDE_T3_MCP_TOOL_WILDCARD],
533536 mcpServers: T3_MCP_SERVERS,
537 mcpEnvironment: T3_MCP_ENVIRONMENT,
534538 });
535539 });
536540 });
......@@ -547,6 +551,7 @@ describe("ClaudeAdapterV2 MCP query overrides", () => {
547551 assert.deepEqual(overrides, {
548552 allowedTools: ["Read", "mcp__t3-code__*"],
549553 mcpServers: T3_MCP_SERVERS,
554 mcpEnvironment: T3_MCP_ENVIRONMENT,
550555 });
551556 });
552557 });
......@@ -566,6 +571,7 @@ describe("ClaudeAdapterV2 MCP query overrides", () => {
566571 ...ClaudeAdapterV2.CLAUDE_READ_ONLY_T3_MCP_ALLOWED_TOOLS,
567572 ],
568573 mcpServers: T3_MCP_SERVERS,
574 mcpEnvironment: T3_MCP_ENVIRONMENT,
569575 });
570576 assert.isFalse(overrides.allowedTools?.includes(ClaudeAdapterV2.CLAUDE_T3_MCP_TOOL_WILDCARD));
571577 });
......@@ -697,11 +703,12 @@ describe("ClaudeAdapterV2 native protocol logging", () => {
697703 type: "http",
698704 url: "http://127.0.0.1:43123/mcp",
699705 headers: {
700 Authorization: "Bearer secret-claude-token",
706 Authorization: "${T3_CODE_MCP_AUTHORIZATION}",
701707 },
702708 timeout: ClaudeAdapterV2.CLAUDE_T3_MCP_TOOL_TIMEOUT_MS,
703709 },
704710 },
711 mcpEnvironment: { T3_CODE_MCP_AUTHORIZATION: "Bearer secret-claude-token" },
705712 });
706713
707714 const options = ClaudeAdapterV2.makeClaudeQueryOptions({
......@@ -712,8 +719,14 @@ describe("ClaudeAdapterV2 native protocol logging", () => {
712719 nativeThreadId: "native-thread-claude-mcp",
713720 resume: false,
714721 cwd: "/workspace",
715 ...overrides,
722 allowedTools: overrides.allowedTools ?? [],
723 mcpServers: overrides.mcpServers ?? {},
724 environment: { ...overrides.mcpEnvironment },
716725 });
726 // mcpServers becomes a CLI argument, readable by every local user; the
727 // credential may only travel in the child's environment.
728 assert.notInclude(JSON.stringify(options.mcpServers), "secret-claude-token");
729 assert.equal(options.env?.T3_CODE_MCP_AUTHORIZATION, "Bearer secret-claude-token");
717730 assert.isObject(options.systemPrompt);
718731 const systemPrompt = options.systemPrompt as {
719732 readonly type: string;
apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts+19-3
......@@ -966,6 +966,13 @@ export const CLAUDE_T3_MCP_TOOL_TIMEOUT_MS = 65 * 60 * 1_000;
966966// not pre-approved), but read-only sandboxes pre-approve only the annotated
967967// read-only orchestrator tools so a read-only session cannot silently spawn
968968// threads or scheduled tasks.
969//
970// The SDK passes `mcpServers` to the CLI as an inline `--mcp-config` argument,
971// and process arguments are readable by every local user. The credential
972// therefore travels in the child's environment, which only its owner can read,
973// and the CLI expands the `${VAR}` reference when it connects.
974const CLAUDE_T3_MCP_AUTHORIZATION_ENV = "T3_CODE_MCP_AUTHORIZATION";
975
969976export function claudeMcpQueryOverrides(input: {
970977 readonly threadId: ThreadId;
971978 readonly readOnlySandbox: boolean;
......@@ -973,6 +980,7 @@ export function claudeMcpQueryOverrides(input: {
973980}): {
974981 readonly allowedTools?: ReadonlyArray<string>;
975982 readonly mcpServers?: ClaudeQueryOptions["mcpServers"];
983 readonly mcpEnvironment?: Readonly<Record<string, string>>;
976984} {
977985 const session = McpProviderSession.readMcpProviderSession(input.threadId);
978986 if (session === undefined) {
......@@ -988,11 +996,12 @@ export function claudeMcpQueryOverrides(input: {
988996 type: "http",
989997 url: session.endpoint,
990998 headers: {
991 Authorization: session.authorizationHeader,
999 Authorization: `\${${CLAUDE_T3_MCP_AUTHORIZATION_ENV}}`,
9921000 },
9931001 timeout: CLAUDE_T3_MCP_TOOL_TIMEOUT_MS,
9941002 },
9951003 },
1004 mcpEnvironment: { [CLAUDE_T3_MCP_AUTHORIZATION_ENV]: session.authorizationHeader },
9961005 };
9971006}
9981007
......@@ -1623,6 +1632,7 @@ export function claudeEffectiveQueryPolicyKey(
16231632 mcpOverrides: {
16241633 readonly allowedTools?: ReadonlyArray<string>;
16251634 readonly mcpServers?: ClaudeQueryOptions["mcpServers"];
1635 readonly mcpEnvironment?: Readonly<Record<string, string>>;
16261636 },
16271637): string {
16281638 return JSON.stringify({
......@@ -1633,6 +1643,7 @@ export function claudeEffectiveQueryPolicyKey(
16331643 : { allowedTools: mcpOverrides.allowedTools }),
16341644 }),
16351645 mcpServers: mcpOverrides.mcpServers,
1646 mcpEnvironment: mcpOverrides.mcpEnvironment,
16361647 });
16371648}
16381649
......@@ -7338,9 +7349,14 @@ export function makeClaudeAdapterV2(
73387349 cwd: turnInput.runtimePolicy.cwd,
73397350 attachmentsDir,
73407351 settings: adapterOptions.settings,
7341 environment: adapterOptions.environment,
7352 environment: { ...adapterOptions.environment, ...mcpOverrides.mcpEnvironment },
73427353 tools: queryPolicy.tools ?? CLAUDE_CODE_PRESET_TOOLS,
7343 ...mcpOverrides,
7354 ...(mcpOverrides.allowedTools === undefined
7355 ? {}
7356 : { allowedTools: mcpOverrides.allowedTools }),
7357 ...(mcpOverrides.mcpServers === undefined
7358 ? {}
7359 : { mcpServers: mcpOverrides.mcpServers }),
73447360 permissionMode: queryPolicy.permissionMode,
73457361 ...(queryPolicy.allowDangerouslySkipPermissions === undefined
73467362 ? {}