Skip to content

fix(mcp): reject requires entries with shell metacharacters - #259

Merged
jeff-r2026 merged 1 commit into
mainfrom
fix/mcp-requires-command-injection
Jul 29, 2026
Merged

fix(mcp): reject requires entries with shell metacharacters#259
jeff-r2026 merged 1 commit into
mainfrom
fix/mcp-requires-command-injection

Conversation

@jeff-r2026

Copy link
Copy Markdown
Collaborator

Problem

requirementsMet in src/mcp-reconcile.ts verifies each requires executable via:

await run('command', ['-v', bin], { shell: '/bin/sh' });

bin comes directly from the team repo's mcp/mcp.yaml. Because command -v runs under a shell, a crafted requires value is executed as shell code — command injection on any member's machine during teamai pull / teamai mcp inject.

servers:
  - name: evil
    transport: stdio
    command: echo
    requires:
      - "npx; rm -rf ~"   # the part after `;` runs

Confirmed exploitable locally: a requires value of echo; touch /tmp/PROOF created the marker file. This is pre-existing (introduced in #252), independent of the MCP secret-writing change in #258.

Change

Validate every requires name against ^[A-Za-z0-9][A-Za-z0-9._-]*$ before it reaches the shell. A genuine executable name has no shell metacharacters, so anything else is rejected with an invalid name skip reason instead of being run. Legitimate entries like npx are unaffected.

Kept the existing command -v mechanism (portable across the shells teamai targets) rather than switching to which/where, since the whitelist already removes the injection surface.

Test Plan

  • npx tsc --noEmit — no errors
  • npx vitest run — 1815 passed (added a test asserting a metacharacter-laden requires is skipped and its injected command never runs)
  • npm run build — success
  • End-to-end against a real reconcile run:
    • requires: ["echo; touch /tmp/E2E_INJECTION_PROOF"] → server skipped (invalid name), marker file NOT created
    • requires: ["npx"] → server added normally

🤖 Generated with Claude Code

`requirementsMet` checks each `requires` executable with
`command -v <bin>` run under `/bin/sh`. `bin` comes straight from the
team repo's mcp.yaml, so a value like `npx; rm -rf ~` is passed to the
shell and executed — a command injection triggered on any member's
machine during `teamai pull` / `teamai mcp inject`.

Validate each name against `^[A-Za-z0-9][A-Za-z0-9._-]*$` before the
shell ever sees it. A real executable name has no shell metacharacters,
so anything else is rejected with an invalid-name skip reason rather
than run. Legitimate `requires` (e.g. `npx`) are unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@jeff-r2026
jeff-r2026 merged commit be32014 into main Jul 29, 2026
7 checks passed
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