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", () => {...@@ -478,11 +478,14 @@ describe("ClaudeAdapterV2 MCP query overrides", () => {
478 type: "http",478 type: "http",
479 url: "http://127.0.0.1:43123/mcp",479 url: "http://127.0.0.1:43123/mcp",
480 headers: {480 headers: {
481 Authorization: "Bearer secret-claude-token",481 Authorization: "${T3_CODE_MCP_AUTHORIZATION}",
482 },482 },
483 timeout: ClaudeAdapterV2.CLAUDE_T3_MCP_TOOL_TIMEOUT_MS,483 timeout: ClaudeAdapterV2.CLAUDE_T3_MCP_TOOL_TIMEOUT_MS,
484 },484 },
485 } as const;485 } as const;
486 const T3_MCP_ENVIRONMENT = {
487 T3_CODE_MCP_AUTHORIZATION: "Bearer secret-claude-token",
488 } as const;
486489
487 const withMcpSession = (threadId: ThreadId, run: () => void) => {490 const withMcpSession = (threadId: ThreadId, run: () => void) => {
488 McpProviderSession.setMcpProviderSession({491 McpProviderSession.setMcpProviderSession({
...@@ -531,6 +534,7 @@ describe("ClaudeAdapterV2 MCP query overrides", () => {...@@ -531,6 +534,7 @@ describe("ClaudeAdapterV2 MCP query overrides", () => {
531 assert.deepEqual(overrides, {534 assert.deepEqual(overrides, {
532 allowedTools: [ClaudeAdapterV2.CLAUDE_T3_MCP_TOOL_WILDCARD],535 allowedTools: [ClaudeAdapterV2.CLAUDE_T3_MCP_TOOL_WILDCARD],
533 mcpServers: T3_MCP_SERVERS,536 mcpServers: T3_MCP_SERVERS,
537 mcpEnvironment: T3_MCP_ENVIRONMENT,
534 });538 });
535 });539 });
536 });540 });
...@@ -547,6 +551,7 @@ describe("ClaudeAdapterV2 MCP query overrides", () => {...@@ -547,6 +551,7 @@ describe("ClaudeAdapterV2 MCP query overrides", () => {
547 assert.deepEqual(overrides, {551 assert.deepEqual(overrides, {
548 allowedTools: ["Read", "mcp__t3-code__*"],552 allowedTools: ["Read", "mcp__t3-code__*"],
549 mcpServers: T3_MCP_SERVERS,553 mcpServers: T3_MCP_SERVERS,
554 mcpEnvironment: T3_MCP_ENVIRONMENT,
550 });555 });
551 });556 });
552 });557 });
...@@ -566,6 +571,7 @@ describe("ClaudeAdapterV2 MCP query overrides", () => {...@@ -566,6 +571,7 @@ describe("ClaudeAdapterV2 MCP query overrides", () => {
566 ...ClaudeAdapterV2.CLAUDE_READ_ONLY_T3_MCP_ALLOWED_TOOLS,571 ...ClaudeAdapterV2.CLAUDE_READ_ONLY_T3_MCP_ALLOWED_TOOLS,
567 ],572 ],
568 mcpServers: T3_MCP_SERVERS,573 mcpServers: T3_MCP_SERVERS,
574 mcpEnvironment: T3_MCP_ENVIRONMENT,
569 });575 });
570 assert.isFalse(overrides.allowedTools?.includes(ClaudeAdapterV2.CLAUDE_T3_MCP_TOOL_WILDCARD));576 assert.isFalse(overrides.allowedTools?.includes(ClaudeAdapterV2.CLAUDE_T3_MCP_TOOL_WILDCARD));
571 });577 });
...@@ -697,11 +703,12 @@ describe("ClaudeAdapterV2 native protocol logging", () => {...@@ -697,11 +703,12 @@ describe("ClaudeAdapterV2 native protocol logging", () => {
697 type: "http",703 type: "http",
698 url: "http://127.0.0.1:43123/mcp",704 url: "http://127.0.0.1:43123/mcp",
699 headers: {705 headers: {
700 Authorization: "Bearer secret-claude-token",706 Authorization: "${T3_CODE_MCP_AUTHORIZATION}",
701 },707 },
702 timeout: ClaudeAdapterV2.CLAUDE_T3_MCP_TOOL_TIMEOUT_MS,708 timeout: ClaudeAdapterV2.CLAUDE_T3_MCP_TOOL_TIMEOUT_MS,
703 },709 },
704 },710 },
711 mcpEnvironment: { T3_CODE_MCP_AUTHORIZATION: "Bearer secret-claude-token" },
705 });712 });
706713
707 const options = ClaudeAdapterV2.makeClaudeQueryOptions({714 const options = ClaudeAdapterV2.makeClaudeQueryOptions({
...@@ -712,8 +719,14 @@ describe("ClaudeAdapterV2 native protocol logging", () => {...@@ -712,8 +719,14 @@ describe("ClaudeAdapterV2 native protocol logging", () => {
712 nativeThreadId: "native-thread-claude-mcp",719 nativeThreadId: "native-thread-claude-mcp",
713 resume: false,720 resume: false,
714 cwd: "/workspace",721 cwd: "/workspace",
715 ...overrides,722 allowedTools: overrides.allowedTools ?? [],
723 mcpServers: overrides.mcpServers ?? {},
724 environment: { ...overrides.mcpEnvironment },
716 });725 });
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");
717 assert.isObject(options.systemPrompt);730 assert.isObject(options.systemPrompt);
718 const systemPrompt = options.systemPrompt as {731 const systemPrompt = options.systemPrompt as {
719 readonly type: string;732 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;...@@ -966,6 +966,13 @@ export const CLAUDE_T3_MCP_TOOL_TIMEOUT_MS = 65 * 60 * 1_000;
966// not pre-approved), but read-only sandboxes pre-approve only the annotated966// not pre-approved), but read-only sandboxes pre-approve only the annotated
967// read-only orchestrator tools so a read-only session cannot silently spawn967// read-only orchestrator tools so a read-only session cannot silently spawn
968// threads or scheduled tasks.968// 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
969export function claudeMcpQueryOverrides(input: {976export function claudeMcpQueryOverrides(input: {
970 readonly threadId: ThreadId;977 readonly threadId: ThreadId;
971 readonly readOnlySandbox: boolean;978 readonly readOnlySandbox: boolean;
...@@ -973,6 +980,7 @@ export function claudeMcpQueryOverrides(input: {...@@ -973,6 +980,7 @@ export function claudeMcpQueryOverrides(input: {
973}): {980}): {
974 readonly allowedTools?: ReadonlyArray<string>;981 readonly allowedTools?: ReadonlyArray<string>;
975 readonly mcpServers?: ClaudeQueryOptions["mcpServers"];982 readonly mcpServers?: ClaudeQueryOptions["mcpServers"];
983 readonly mcpEnvironment?: Readonly<Record<string, string>>;
976} {984} {
977 const session = McpProviderSession.readMcpProviderSession(input.threadId);985 const session = McpProviderSession.readMcpProviderSession(input.threadId);
978 if (session === undefined) {986 if (session === undefined) {
...@@ -988,11 +996,12 @@ export function claudeMcpQueryOverrides(input: {...@@ -988,11 +996,12 @@ export function claudeMcpQueryOverrides(input: {
988 type: "http",996 type: "http",
989 url: session.endpoint,997 url: session.endpoint,
990 headers: {998 headers: {
991 Authorization: session.authorizationHeader,999 Authorization: `\${${CLAUDE_T3_MCP_AUTHORIZATION_ENV}}`,
992 },1000 },
993 timeout: CLAUDE_T3_MCP_TOOL_TIMEOUT_MS,1001 timeout: CLAUDE_T3_MCP_TOOL_TIMEOUT_MS,
994 },1002 },
995 },1003 },
1004 mcpEnvironment: { [CLAUDE_T3_MCP_AUTHORIZATION_ENV]: session.authorizationHeader },
996 };1005 };
997}1006}
9981007
...@@ -1623,6 +1632,7 @@ export function claudeEffectiveQueryPolicyKey(...@@ -1623,6 +1632,7 @@ export function claudeEffectiveQueryPolicyKey(
1623 mcpOverrides: {1632 mcpOverrides: {
1624 readonly allowedTools?: ReadonlyArray<string>;1633 readonly allowedTools?: ReadonlyArray<string>;
1625 readonly mcpServers?: ClaudeQueryOptions["mcpServers"];1634 readonly mcpServers?: ClaudeQueryOptions["mcpServers"];
1635 readonly mcpEnvironment?: Readonly<Record<string, string>>;
1626 },1636 },
1627): string {1637): string {
1628 return JSON.stringify({1638 return JSON.stringify({
...@@ -1633,6 +1643,7 @@ export function claudeEffectiveQueryPolicyKey(...@@ -1633,6 +1643,7 @@ export function claudeEffectiveQueryPolicyKey(
1633 : { allowedTools: mcpOverrides.allowedTools }),1643 : { allowedTools: mcpOverrides.allowedTools }),
1634 }),1644 }),
1635 mcpServers: mcpOverrides.mcpServers,1645 mcpServers: mcpOverrides.mcpServers,
1646 mcpEnvironment: mcpOverrides.mcpEnvironment,
1636 });1647 });
1637}1648}
16381649
...@@ -7338,9 +7349,14 @@ export function makeClaudeAdapterV2(...@@ -7338,9 +7349,14 @@ export function makeClaudeAdapterV2(
7338 cwd: turnInput.runtimePolicy.cwd,7349 cwd: turnInput.runtimePolicy.cwd,
7339 attachmentsDir,7350 attachmentsDir,
7340 settings: adapterOptions.settings,7351 settings: adapterOptions.settings,
7341 environment: adapterOptions.environment,7352 environment: { ...adapterOptions.environment, ...mcpOverrides.mcpEnvironment },
7342 tools: queryPolicy.tools ?? CLAUDE_CODE_PRESET_TOOLS,7353 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 }),
7344 permissionMode: queryPolicy.permissionMode,7360 permissionMode: queryPolicy.permissionMode,
7345 ...(queryPolicy.allowDangerouslySkipPermissions === undefined7361 ...(queryPolicy.allowDangerouslySkipPermissions === undefined
7346 ? {}7362 ? {}