Skip to content

Conversation

@cameroncooke
Copy link
Collaborator

@cameroncooke cameroncooke commented Feb 2, 2026

Breaking

  • To run as an MCP server, the mcp argument must be passed

New

  • XcodeBuildMCP is now a CLI
  • CLI commands are stateful by means of a macOS daemon

Note

Medium Risk
Medium risk due to a breaking invocation change (xcodebuildmcp mcp) and updates to CLI/daemon startup + command spawning that can affect how tools execute and connect to the daemon.

Overview
Shifts xcodebuildmcp to a unified CLI-first interface where MCP server mode is started explicitly via the mcp subcommand, and updates all user/developer docs and install snippets to pass mcp (including quick-install links and testing commands).

Adds a new docs/CLI.md guide describing direct tool invocation, daemon lifecycle commands, and per-workspace daemon behavior; updates architecture docs accordingly.

Includes several CLI/daemon fixes called out in the changelog: honor XCODEBUILDMCP_SOCKET overrides when auto-starting the daemon, avoid daemon crashes by stopping log-file output after stream errors, and stop routing tool commands through sh by default (preventing spawn sh ENOENT).

Written by Cursor Bugbot for commit b04c37a. This will update automatically on new commits. Configure here.

// Log the actual command that will be executed
const displayCommand =
useShell && escapedCommand.length === 3 ? escapedCommand[2] : [executable, ...args].join(' ');
log('info', `Executing ${logPrefix ?? ''} command: ${displayCommand}`);

Check warning

Code scanning / CodeQL

Shell command built from environment values Medium

This shell command depends on an uncontrolled
absolute path
.
This shell command depends on an uncontrolled
absolute path
.
This shell command depends on an uncontrolled
absolute path
.
This shell command depends on an uncontrolled
absolute path
.
This shell command depends on an uncontrolled
absolute path
.
This shell command depends on an uncontrolled
absolute path
.
This shell command depends on an uncontrolled
absolute path
.
This shell command depends on an uncontrolled
absolute path
.
This shell command depends on an uncontrolled
absolute path
.
This shell command depends on an uncontrolled absolute path.
This shell command depends on an uncontrolled absolute path.
This shell command depends on an uncontrolled absolute path.
This shell command depends on an uncontrolled absolute path.

Copilot Autofix

AI 10 days ago

General approach: avoid running paths derived from process.cwd(), configuration, or PATH through a shell command string. Instead, pass the executable path and its arguments directly to spawn, letting Node escape/handle them without shell interpretation. For functionality that currently calls the executor with useShell: true for simple command + args, switch to useShell: false. This completely sidesteps the need to manually quote arguments and eliminates shell injection from tainted paths.

Best concrete fix here: the problematic flow originates from isAxeAtLeastVersion, which calls the executor with true for useShell. That causes [axePath, '--version'] to be converted into a single /bin/sh -c command string, which is what CodeQL flags at spawn(executable, args, spawnOpts). There is no need for shell semantics when running axe --version; it is just an executable with one argument. Changing that call to use false (or omit the flag and rely on the default) ensures that defaultExecutor invokes spawn(axePath, ['--version'], spawnOpts) without the intermediate shell command string, removing the vulnerability. This change preserves all existing functionality: the same executable is run with the same argument, we just stop involving /bin/sh -c.

Concretely:

  • In src/utils/axe-helpers.ts, edit isAxeAtLeastVersion so that it calls exec([axePath, '--version'], 'AXe Version', false) instead of passing true. No other logic changes are needed.
  • No changes are required in src/utils/command.ts itself, because the non-shell path is already safe.
Suggested changeset 1
src/utils/axe-helpers.ts
Outside changed files

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/utils/axe-helpers.ts b/src/utils/axe-helpers.ts
--- a/src/utils/axe-helpers.ts
+++ b/src/utils/axe-helpers.ts
@@ -162,7 +162,8 @@
 
   const exec = executor ?? getDefaultCommandExecutor();
   try {
-    const res = await exec([axePath, '--version'], 'AXe Version', true);
+    // Use direct execution without a shell to avoid shell interpretation of the axe path.
+    const res = await exec([axePath, '--version'], 'AXe Version', false);
     if (!res.success) return false;
 
     const output = res.output ?? '';
EOF
@@ -162,7 +162,8 @@

const exec = executor ?? getDefaultCommandExecutor();
try {
const res = await exec([axePath, '--version'], 'AXe Version', true);
// Use direct execution without a shell to avoid shell interpretation of the axe path.
const res = await exec([axePath, '--version'], 'AXe Version', false);
if (!res.success) return false;

const output = res.output ?? '';
Copilot is powered by AI and may make mistakes. Always verify output.
@pkg-pr-new
Copy link

pkg-pr-new bot commented Feb 2, 2026

Open in StackBlitz

npm i https://pkg.pr.new/cameroncooke/XcodeBuildMCP/xcodebuildmcp@197

commit: b04c37a

@cameroncooke cameroncooke merged commit b1ef7b3 into main Feb 2, 2026
8 checks passed
@cameroncooke cameroncooke deleted the XcodeBuildCLI branch February 2, 2026 23:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant