Eight Months, Three Follow-Ups, Zero Replies: mac-shell-mcp’s Whitelist Bypass Outlived Its Own Fix

NVD published CVE-2026-108953 at 13:17Z today against mac-shell-mcp — an MCP server for executing macOS terminal commands, advertising itself as “a secure way to execute shell commands with built-in whitelisting and approval mechanisms” — carrying 7.8 high on CVSS 3.1 (CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H) and 8.5 high on CVSS 4.0, affecting versions before 2.0.0. The description is unusually explicit about the threat model: MCP clients bypass the command whitelist by injecting shell metacharacters into execute_command arguments, and “attackers or malicious prompts can call a safe command such as pwd with arguments like ';' to run forbidden commands as the server user without approval.” A prompt — text the model read somewhere — is listed alongside the attacker as a caller of the exploit. That single phrase is the whole MCP security problem in one sentence.

The disclosure history, readable first-hand in the repository’s own issue tracker, is the other half of the story. On 4 October, the researcher hackwither opened issue #45, stating they had reported the CWE-78 flaw privately via GitHub Security Advisories on 28 January (GHSA-2h3j-235x-vx2q), followed up on 13 June and again the previous week, and had not heard back — asking the maintainer to publish the advisory and requesting that, absent a reply by 10 October, a CVE be sought through a third-party CNA. The issue sits at zero comments. The NVD record’s assigning source is disclosure@vulncheck.com: the 10 October deadline passed, and VulnCheck — acting as CNA, as it did for this morning’s mcp-go record — issued the identifier. Eight and a half months from private report to public record, with the middle of the timeline silent.

A whitelist in front of a shell is a suggestion

The vulnerable design is the one this ecosystem keeps re-installing: a policy layer (which commands are allowed) placed in front of an execution layer (a shell) that reinterprets the policy layer’s output. Approving pwd means nothing once the argument string can carry ';' and everything after it, because the shell — not the whitelist — decides where one command ends and the next begins. The NVD description’s example is deliberately minimal: no exotic operator, no encoding trick, just the statement terminator doing what statement terminators do. Any operator-screening approach to this bug would be the same substring-filter game agno just lost and then correctly abandoned in favour of removing the shell (last night’s batch).

To the maintainer’s credit, the eventual fix — which predates the CVE by six weeks — takes exactly that lesson. Commit 932ce73 (29 August, PR #20), titled feat(security)!: rebuild on confinement, allowlists and no shell (2.0.0), rebuilds the server on confinement with no shell interpretation, deletes seven tools outright (add_to_whitelist, update_security_level, remove_from_whitelist, approve_command, deny_command, get_pending_commands, get_whitelist), and adds a path guard, a policy module and an audit log. The commit message notes it also closes a CWE-862 missing-authorisation issue: no tool mutates policy anymore, and the approval queue — with its promise that never timed out — is gone rather than gated. Removing the mechanism that cannot be secured, instead of screening its inputs harder, is the correct call, and it is worth saying so precisely because the disclosure handling around it was not.

The fix exists where the users are not looking

Here is the distribution anomaly, verified first-hand across three channels this afternoon. The fixed code is obtainable: npm lists @the_cfdude/mac-shell-mcp at 2.0.0, published 24 September, and package.json on main already reads 2.0.1. But the GitHub repository — the channel a security scanner, a Dependabot alert, or a human checking for patches actually watches — shows no tag beyond v1.1.0 and zero releases. Anyone tracking this project through tags or the releases page concludes, reasonably and wrongly, that nothing newer than the vulnerable 1.x line exists. This is the mirror image of the PraisonAI shape (advisories naming a version that was never published) and the JeecgBoot shape (a fix that exists only on main): here the artefact shipped to the registry while the repository’s own version signalling stayed frozen eight months into a known command-injection report. Small projects (25 stars, 14 forks) do not get the benefit of scanners that reconcile npm against GitHub; their users get exactly the signalling the maintainer publishes, and here it says 1.1.0.

The composition point, finally, is the one the NVD description already makes. An MCP shell server’s “client” is frequently a model acting on retrieved content — tool outputs, web pages, repository files — which means the whitelist’s adversary is not only the operator typing at a prompt but any text the agent was ever told to treat as instructions. “Without approval” in the description cuts deepest here: the approval mechanism was one of the seven deleted tools, which tells you what the pre-2.0 design believed stood between a prompt and a shell. When the control that gates execution can itself be reached — and removed — through the tool interface, it was furniture, not a boundary.

What to do, with versions that actually exist

  • Upgrade to npm 2.0.0 or later. Do not wait for a GitHub release or tag that may never come; the fixed line is the 2.x rebuild with no shell interpretation. Note the breaking change: seven policy/approval tools are gone, so integrations that called them need rework.
  • If you track this project via GitHub, switch the watch to the registry. Tags stop at v1.1.0 and there are no releases; tag-based automation will never offer you the fix.
  • Treat pre-2.0 approvals as non-controls. Any deployment that relied on the whitelist or the approval queue to contain a model-driven client was containing nothing once metacharacters passed through. Audit what ran under 1.x with that assumption.

Verification note. The CVE identifier, publication timestamp (13:17:28Z, 12 October 2026), CVSS 3.1 and 4.0 scores and vectors, affected-before version and vulnStatus: Received were read first-hand from the NVD 2.0 API, as was the assigning source disclosure@vulncheck.com. The disclosure timeline (GHSA filed 28 January, follow-ups 13 June and late September, 10 October third-party-CNA deadline) and the zero-comment state are the reporter’s published statements, read first-hand from issue #45 via the GitHub API and attributed as such. The fix-commit message, tool-removal list and new-module names were read first-hand from commit 932ce73 via the GitHub API. Distribution claims were verified first-hand: npm registry metadata (latest 2.0.0, 24 September), package.json at main (2.0.1), the tags API (v1.1.0 newest) and the releases API (empty). Star, fork and push data from the GitHub API. We did not install the server, execute any command through it, or contact the maintainer. The whitelist-before-shell analysis and the distribution-signalling reading are our editorial analysis. We have no evidence of exploitation and imply none.

Sources: