Skip to content

Commit c352b33

Browse files
committed
fix(base-action): default enableAllProjectMcpServers to false (H1 #3639058)
The base-action previously hardcoded enableAllProjectMcpServers=true in ~/.claude/settings.json. In pull_request workflows the checkout's .mcp.json is PR-controlled, so a malicious PR could drop a stdio MCP server and have it auto-spawned as a subprocess on the runner — arbitrary command execution before any tool-permission gating applies. This change: - defaults enableAllProjectMcpServers to false in the base-action - adds an enable_all_project_mcp_servers input so trusted workflows can opt back in - threads the input through the wrapper action, which keeps a 'true' default because it already restores .mcp.json from the PR base branch Complements PR #1115 (setting_sources default change) — same threat surface, different config knob. :house: Remote-Dev: homespace
1 parent 6ee201f commit c352b33

7 files changed

Lines changed: 66 additions & 35 deletions

File tree

action.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ inputs:
6262
description: "Claude Code settings as JSON string or path to settings JSON file"
6363
required: false
6464
default: ""
65+
enable_all_project_mcp_servers:
66+
description: "Auto-enable every MCP server defined in the checkout's .mcp.json. Defaults to 'true' — safe here because .mcp.json is restored from the PR base branch before execution. Set to 'false' to ignore in-repo MCP servers entirely."
67+
required: false
68+
default: "true"
6569

6670
# Auth configuration
6771
anthropic_api_key:
@@ -279,6 +283,7 @@ runs:
279283
# Base-action inputs
280284
INPUT_PROMPT_FILE: ${{ runner.temp }}/claude-prompts/claude-prompt.txt
281285
INPUT_SETTINGS: ${{ inputs.settings }}
286+
INPUT_ENABLE_ALL_PROJECT_MCP_SERVERS: ${{ inputs.enable_all_project_mcp_servers }}
282287
INPUT_EXPERIMENTAL_SLASH_COMMANDS_DIR: ${{ github.action_path }}/slash-commands
283288
INPUT_PATH_TO_CLAUDE_CODE_EXECUTABLE: ${{ inputs.path_to_claude_code_executable }}
284289
INPUT_PATH_TO_BUN_EXECUTABLE: ${{ inputs.path_to_bun_executable }}

base-action/README.md

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -85,27 +85,28 @@ Add the following to your workflow file:
8585
8686
## Inputs
8787
88-
| Input | Description | Required | Default |
89-
| ------------------------- | ----------------------------------------------------------------------------------------------------------------------- | -------- | ---------------------------- |
90-
| `prompt` | The prompt to send to Claude Code | No\* | '' |
91-
| `prompt_file` | Path to a file containing the prompt to send to Claude Code | No\* | '' |
92-
| `allowed_tools` | Comma-separated list of allowed tools for Claude Code to use | No | '' |
93-
| `disallowed_tools` | Comma-separated list of disallowed tools that Claude Code cannot use | No | '' |
94-
| `max_turns` | Maximum number of conversation turns (default: no limit) | No | '' |
95-
| `mcp_config` | Path to the MCP configuration JSON file, or MCP configuration JSON string | No | '' |
96-
| `settings` | Path to Claude Code settings JSON file, or settings JSON string | No | '' |
97-
| `system_prompt` | Override system prompt | No | '' |
98-
| `append_system_prompt` | Append to system prompt | No | '' |
99-
| `claude_env` | Custom environment variables to pass to Claude Code execution (YAML multiline format) | No | '' |
100-
| `model` | Model to use (provider-specific format required for Bedrock/Vertex) | No | 'claude-4-0-sonnet-20250219' |
101-
| `anthropic_model` | DEPRECATED: Use 'model' instead | No | 'claude-4-0-sonnet-20250219' |
102-
| `fallback_model` | Enable automatic fallback to specified model when default model is overloaded | No | '' |
103-
| `anthropic_api_key` | Anthropic API key (required for direct Anthropic API) | No | '' |
104-
| `claude_code_oauth_token` | Claude Code OAuth token (alternative to anthropic_api_key) | No | '' |
105-
| `use_bedrock` | Use Amazon Bedrock with OIDC authentication instead of direct Anthropic API | No | 'false' |
106-
| `use_vertex` | Use Google Vertex AI with OIDC authentication instead of direct Anthropic API | No | 'false' |
107-
| `use_node_cache` | Whether to use Node.js dependency caching (set to true only for Node.js projects with lock files) | No | 'false' |
108-
| `show_full_output` | Show full JSON output (⚠️ May expose secrets - see [security docs](../docs/security.md#️-full-output-security-warning)) | No | 'false'\*\* |
88+
| Input | Description | Required | Default |
89+
| -------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | -------- | ---------------------------- |
90+
| `prompt` | The prompt to send to Claude Code | No\* | '' |
91+
| `prompt_file` | Path to a file containing the prompt to send to Claude Code | No\* | '' |
92+
| `allowed_tools` | Comma-separated list of allowed tools for Claude Code to use | No | '' |
93+
| `disallowed_tools` | Comma-separated list of disallowed tools that Claude Code cannot use | No | '' |
94+
| `max_turns` | Maximum number of conversation turns (default: no limit) | No | '' |
95+
| `mcp_config` | Path to the MCP configuration JSON file, or MCP configuration JSON string | No | '' |
96+
| `settings` | Path to Claude Code settings JSON file, or settings JSON string | No | '' |
97+
| `enable_all_project_mcp_servers` | Auto-enable every MCP server in the checkout's `.mcp.json`. Off by default because `.mcp.json` is PR-controlled. | No | 'false' |
98+
| `system_prompt` | Override system prompt | No | '' |
99+
| `append_system_prompt` | Append to system prompt | No | '' |
100+
| `claude_env` | Custom environment variables to pass to Claude Code execution (YAML multiline format) | No | '' |
101+
| `model` | Model to use (provider-specific format required for Bedrock/Vertex) | No | 'claude-4-0-sonnet-20250219' |
102+
| `anthropic_model` | DEPRECATED: Use 'model' instead | No | 'claude-4-0-sonnet-20250219' |
103+
| `fallback_model` | Enable automatic fallback to specified model when default model is overloaded | No | '' |
104+
| `anthropic_api_key` | Anthropic API key (required for direct Anthropic API) | No | '' |
105+
| `claude_code_oauth_token` | Claude Code OAuth token (alternative to anthropic_api_key) | No | '' |
106+
| `use_bedrock` | Use Amazon Bedrock with OIDC authentication instead of direct Anthropic API | No | 'false' |
107+
| `use_vertex` | Use Google Vertex AI with OIDC authentication instead of direct Anthropic API | No | 'false' |
108+
| `use_node_cache` | Whether to use Node.js dependency caching (set to true only for Node.js projects with lock files) | No | 'false' |
109+
| `show_full_output` | Show full JSON output (⚠️ May expose secrets - see [security docs](../docs/security.md#️-full-output-security-warning)) | No | 'false'\*\* |
109110

110111
\*Either `prompt` or `prompt_file` must be provided, but not both.
111112

base-action/action.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ inputs:
1818
description: "Claude Code settings as JSON string or path to settings JSON file"
1919
required: false
2020
default: ""
21+
enable_all_project_mcp_servers:
22+
description: "Auto-enable every MCP server defined in the checkout's .mcp.json. Defaults to 'false' because .mcp.json is PR-controlled in pull_request workflows; set to 'true' only when the checkout is trusted."
23+
required: false
24+
default: "false"
2125

2226
# Action settings
2327
claude_args:
@@ -165,6 +169,7 @@ runs:
165169
INPUT_PROMPT: ${{ inputs.prompt }}
166170
INPUT_PROMPT_FILE: ${{ inputs.prompt_file }}
167171
INPUT_SETTINGS: ${{ inputs.settings }}
172+
INPUT_ENABLE_ALL_PROJECT_MCP_SERVERS: ${{ inputs.enable_all_project_mcp_servers }}
168173
INPUT_CLAUDE_ARGS: ${{ inputs.claude_args }}
169174
INPUT_PATH_TO_CLAUDE_CODE_EXECUTABLE: ${{ inputs.path_to_claude_code_executable }}
170175
INPUT_PATH_TO_BUN_EXECUTABLE: ${{ inputs.path_to_bun_executable }}

base-action/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ async function run() {
2222
await setupClaudeCodeSettings(
2323
process.env.INPUT_SETTINGS,
2424
undefined, // homeDir
25+
process.env.INPUT_ENABLE_ALL_PROJECT_MCP_SERVERS === "true",
2526
);
2627

2728
// Install Claude Code plugins if specified

base-action/src/setup-claude-code-settings.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { readFile } from "fs/promises";
55
export async function setupClaudeCodeSettings(
66
settingsInput?: string,
77
homeDir?: string,
8+
enableAllProjectMcpServers: boolean = false,
89
) {
910
const home = homeDir ?? homedir();
1011
const settingsPath = `${home}/.claude/settings.json`;
@@ -59,9 +60,14 @@ export async function setupClaudeCodeSettings(
5960
console.log(`Merged settings with input settings`);
6061
}
6162

62-
// Always set enableAllProjectMcpServers to true
63-
settings.enableAllProjectMcpServers = true;
64-
console.log(`Updated settings with enableAllProjectMcpServers: true`);
63+
// Default enableAllProjectMcpServers to false: when true, Claude Code will
64+
// auto-spawn every server in the checkout's .mcp.json, which is PR-controlled
65+
// in pull_request workflows and yields arbitrary command execution on the
66+
// runner. Workflows that trust the checkout must opt in explicitly.
67+
settings.enableAllProjectMcpServers = enableAllProjectMcpServers;
68+
console.log(
69+
`Updated settings with enableAllProjectMcpServers: ${enableAllProjectMcpServers}`,
70+
);
6571

6672
await $`echo ${JSON.stringify(settings, null, 2)} > ${settingsPath}`.quiet();
6773
console.log(`Settings saved successfully`);

base-action/test/setup-claude-code-settings.test.ts

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,21 @@ describe("setupClaudeCodeSettings", () => {
2727
await rm(testHomeDir, { recursive: true, force: true });
2828
});
2929

30-
test("should always set enableAllProjectMcpServers to true when no input", async () => {
30+
test("should default enableAllProjectMcpServers to false when no input", async () => {
3131
await setupClaudeCodeSettings(undefined, testHomeDir);
3232

3333
const settingsContent = await readFile(settingsPath, "utf-8");
3434
const settings = JSON.parse(settingsContent);
3535

36+
expect(settings.enableAllProjectMcpServers).toBe(false);
37+
});
38+
39+
test("should set enableAllProjectMcpServers to true when explicitly opted in", async () => {
40+
await setupClaudeCodeSettings(undefined, testHomeDir, true);
41+
42+
const settingsContent = await readFile(settingsPath, "utf-8");
43+
const settings = JSON.parse(settingsContent);
44+
3645
expect(settings.enableAllProjectMcpServers).toBe(true);
3746
});
3847

@@ -47,7 +56,7 @@ describe("setupClaudeCodeSettings", () => {
4756
const settingsContent = await readFile(settingsPath, "utf-8");
4857
const settings = JSON.parse(settingsContent);
4958

50-
expect(settings.enableAllProjectMcpServers).toBe(true);
59+
expect(settings.enableAllProjectMcpServers).toBe(false);
5160
expect(settings.model).toBe("claude-sonnet-4-20250514");
5261
expect(settings.env).toEqual({ API_KEY: "test-key" });
5362
});
@@ -74,23 +83,23 @@ describe("setupClaudeCodeSettings", () => {
7483
const settingsContent = await readFile(settingsPath, "utf-8");
7584
const settings = JSON.parse(settingsContent);
7685

77-
expect(settings.enableAllProjectMcpServers).toBe(true);
86+
expect(settings.enableAllProjectMcpServers).toBe(false);
7887
expect(settings.hooks).toEqual(testSettings.hooks);
7988
expect(settings.permissions).toEqual(testSettings.permissions);
8089
});
8190

82-
test("should override enableAllProjectMcpServers even if false in input", async () => {
91+
test("should override enableAllProjectMcpServers from settings input with action input", async () => {
8392
const inputSettings = JSON.stringify({
84-
enableAllProjectMcpServers: false,
93+
enableAllProjectMcpServers: true,
8594
model: "test-model",
8695
});
8796

88-
await setupClaudeCodeSettings(inputSettings, testHomeDir);
97+
await setupClaudeCodeSettings(inputSettings, testHomeDir, false);
8998

9099
const settingsContent = await readFile(settingsPath, "utf-8");
91100
const settings = JSON.parse(settingsContent);
92101

93-
expect(settings.enableAllProjectMcpServers).toBe(true);
102+
expect(settings.enableAllProjectMcpServers).toBe(false);
94103
expect(settings.model).toBe("test-model");
95104
});
96105

@@ -112,7 +121,7 @@ describe("setupClaudeCodeSettings", () => {
112121
const settingsContent = await readFile(settingsPath, "utf-8");
113122
const settings = JSON.parse(settingsContent);
114123

115-
expect(settings.enableAllProjectMcpServers).toBe(true);
124+
expect(settings.enableAllProjectMcpServers).toBe(false);
116125
});
117126

118127
test("should handle whitespace-only input", async () => {
@@ -121,7 +130,7 @@ describe("setupClaudeCodeSettings", () => {
121130
const settingsContent = await readFile(settingsPath, "utf-8");
122131
const settings = JSON.parse(settingsContent);
123132

124-
expect(settings.enableAllProjectMcpServers).toBe(true);
133+
expect(settings.enableAllProjectMcpServers).toBe(false);
125134
});
126135

127136
test("should merge with existing settings", async () => {
@@ -142,7 +151,7 @@ describe("setupClaudeCodeSettings", () => {
142151
const settingsContent = await readFile(settingsPath, "utf-8");
143152
const settings = JSON.parse(settingsContent);
144153

145-
expect(settings.enableAllProjectMcpServers).toBe(true);
154+
expect(settings.enableAllProjectMcpServers).toBe(false);
146155
expect(settings.existingKey).toBe("existingValue");
147156
expect(settings.newKey).toBe("newValue");
148157
expect(settings.model).toBe("claude-opus-4-1-20250805");

src/entrypoints/run.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,11 @@ async function run() {
256256
}
257257
}
258258

259-
await setupClaudeCodeSettings(process.env.INPUT_SETTINGS);
259+
await setupClaudeCodeSettings(
260+
process.env.INPUT_SETTINGS,
261+
undefined, // homeDir
262+
process.env.INPUT_ENABLE_ALL_PROJECT_MCP_SERVERS === "true",
263+
);
260264

261265
await installPlugins(
262266
process.env.INPUT_PLUGIN_MARKETPLACES,

0 commit comments

Comments
 (0)