Skip to content

Commit 9d868dc

Browse files
QUSETIONSclaude
andcommitted
security: require explicit opt-in for project .mcp.json (issue #13)
Project-level .mcp.json files are no longer auto-loaded — a cloned project could define a malicious MCP server (e.g. curl | sh). Now requires: - CLI: --trust-project-mcp flag - Env: MINI_CODE_TRUST_PROJECT_MCP=1 A warning is logged when a project .mcp.json exists but is NOT loaded. Also fix ruff F841 (unused in paper_a_task_completion_eval). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 4ac055b commit 9d868dc

4 files changed

Lines changed: 1018 additions & 5 deletions

File tree

minicode/config.py

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import annotations
22

3+
import inspect
34
import json
45
import os
56
from pathlib import Path
@@ -367,10 +368,28 @@ def merge_settings(base: dict[str, Any], override: dict[str, Any]) -> dict[str,
367368
}
368369

369370

370-
def load_effective_settings(cwd: str | Path | None = None) -> dict[str, Any]:
371+
def load_effective_settings(
372+
cwd: str | Path | None = None,
373+
*,
374+
trust_project_mcp: bool = False,
375+
) -> dict[str, Any]:
371376
claude_settings = read_settings_file(CLAUDE_SETTINGS_PATH)
372377
global_mcp = read_mcp_config_file(MINI_CODE_MCP_PATH)
373-
project_mcp = read_mcp_config_file(project_mcp_path(cwd))
378+
379+
# Security (issue #13): project-level .mcp.json is NOT loaded by default.
380+
# A cloned project could define a malicious MCP server (e.g. curl | sh).
381+
# Require explicit opt-in via --trust-project-mcp flag or env var.
382+
project_mcp: dict[str, Any] = {}
383+
pmp = project_mcp_path(cwd)
384+
if trust_project_mcp:
385+
project_mcp = read_mcp_config_file(pmp)
386+
elif pmp.exists():
387+
import logging
388+
logging.getLogger("minicode.config").warning(
389+
"Project .mcp.json found at %s but NOT loaded (security: use "
390+
"--trust-project-mcp or MINI_CODE_TRUST_PROJECT_MCP=1).", pmp,
391+
)
392+
374393
mini_code_settings = read_settings_file(MINI_CODE_SETTINGS_PATH)
375394

376395
return merge_settings(
@@ -392,8 +411,28 @@ def save_mini_code_settings(updates: dict[str, Any]) -> None:
392411
)
393412

394413

395-
def load_runtime_config(cwd: str | Path | None = None) -> dict[str, Any]:
396-
effective = load_effective_settings(cwd)
414+
def load_runtime_config(
415+
cwd: str | Path | None = None,
416+
*,
417+
trust_project_mcp: bool | None = None,
418+
) -> dict[str, Any]:
419+
if trust_project_mcp is None:
420+
trust_project_mcp = os.environ.get("MINI_CODE_TRUST_PROJECT_MCP", "").strip().lower() in (
421+
"1", "true", "yes", "on",
422+
)
423+
load_effective = load_effective_settings
424+
try:
425+
signature = inspect.signature(load_effective)
426+
except (TypeError, ValueError):
427+
signature = None
428+
accepts_trust_project_mcp = signature is None or any(
429+
parameter.kind is inspect.Parameter.VAR_KEYWORD or name == "trust_project_mcp"
430+
for name, parameter in signature.parameters.items()
431+
)
432+
if accepts_trust_project_mcp:
433+
effective = load_effective(cwd, trust_project_mcp=trust_project_mcp)
434+
else:
435+
effective = load_effective(cwd)
397436
settings_env = dict(effective.get("env", {}))
398437
env = {**settings_env, **os.environ}
399438

minicode/main.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,11 @@ def main() -> None:
322322
action="store_true",
323323
help="Emit JSON structured logs (also enabled via MINI_CODE_LOG_STRUCTURED=true)",
324324
)
325+
parser.add_argument(
326+
"--trust-project-mcp",
327+
action="store_true",
328+
help="Load project-level .mcp.json (disabled by default for security; also via MINI_CODE_TRUST_PROJECT_MCP=1)",
329+
)
325330

326331
args, remaining_argv = parser.parse_known_args()
327332
if remaining_argv and not any(not arg.startswith("--") for arg in remaining_argv):
@@ -385,7 +390,7 @@ def main() -> None:
385390

386391
runtime = None
387392
try:
388-
runtime = load_runtime_config(cwd)
393+
runtime = load_runtime_config(cwd, trust_project_mcp=args.trust_project_mcp)
389394
except Exception as e: # noqa: BLE001
390395
runtime = None
391396
print(

0 commit comments

Comments
 (0)