CVE-2025-54073
Description
mcp-package-docs is an MCP (Model Context Protocol) server that provides LLMs with efficient access to package documentation across multiple programming languages and language server protocol (LSP) capabilities. A command injection vulnerability exists in the mcp-package-docs MCP Server prior to the fix in commit cb4ad49615275379fd6f2f1cf1ec4731eec56eb9. The vulnerability is caused by the unsanitized use of input parameters within a call to child_process.exec, enabling an attacker to inject arbitrary system commands. Successful exploitation can lead to remote code execution under the server process's privileges. The server constructs and executes shell commands using unvalidated user input directly within command-line strings. This introduces the possibility of shell metacharacter injection (|, >, &&, etc.). Commit cb4ad49615275379fd6f2f1cf1ec4731eec56eb9 in version 0.1.27 contains a fix for the issue, but upgrading to 0.1.28 is recommended.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
mcp-package-docsnpm | < 0.1.28 | 0.1.28 |
Affected products
1- Range: v0.1.0, v0.1.1, v0.1.10, …
Patches
3cb4ad4961527fix(security): fix GHSA-vf9j-h32g-2764 (#13)
4 files changed · +75 −46
package.json+11 −11 modified@@ -49,30 +49,30 @@ }, "homepage": "https://github.com/sammcj/mcp-package-docs#readme", "dependencies": { - "@modelcontextprotocol/sdk": "^1.7.0", - "axios": "^1.8.3", + "@modelcontextprotocol/sdk": "^1.16.0", + "axios": "^1.10.0", "fuse.js": "7.1.0", "node-html-markdown": "1.3.0", - "typescript": "5.8.2", + "typescript": "5.8.3", "typescript-language-server": "^4.3.4", "vscode-languageserver-protocol": "^3.17.5" }, "devDependencies": { "@eslint/js": "10.0.0", - "@types/node": "^22.13.10", + "@types/node": "^22.16.4", "@types/turndown": "5.0.5", - "@typescript-eslint/eslint-plugin": "8.26.1", - "@typescript-eslint/parser": "8.26.1", - "eslint": "9.22.0", - "globals": "16.0.0", - "prettier": "^3.5.3", - "typescript-eslint": "8.26.1" + "@typescript-eslint/eslint-plugin": "8.37.0", + "@typescript-eslint/parser": "8.37.0", + "eslint": "9.31.0", + "globals": "16.3.0", + "prettier": "^3.6.2", + "typescript-eslint": "8.37.0" }, "engines": { "node": ">=20" }, "publishConfig": { "access": "public" }, - "packageManager": "pnpm@10.7.0+sha512.6b865ad4b62a1d9842b61d674a393903b871d9244954f652b8842c2b553c72176b278f64c463e52d40fff8aba385c235c8c9ecf5cc7de4fd78b8bb6d49633ab6" + "packageManager": "pnpm@10.13.1+sha512.37ebf1a5c7a30d5fabe0c5df44ee8da4c965ca0c5af3dbab28c3a1681b70a256218d05c81c9c0dcf767ef6b8551eb5b960042b9ed4300c59242336377e01cfad" }
README.md+3 −11 modified@@ -2,9 +2,7 @@ An MCP (Model Context Protocol) server that provides LLMs with efficient access to package documentation across multiple programming languages and language server protocol (LSP) capabilities. -[](https://smithery.ai/server/mcp-package-docs) - -<a href="https://glama.ai/mcp/servers/mrk7ul7nz7"><img width="380" height="200" src="https://glama.ai/mcp/servers/mrk7ul7nz7/badge" alt="Package Docs Server MCP server" /></a> +**_Note: I am not actively maintaining the codebase at present. While it doesn't provide access to private package documentation - the [Context7](https://github.com/upstash/context7) MCP server and service meets my needs which are mostly for public package documentation. I personally use Context7 via my [mcp-devtools](https://github.com/sammcj/mcp-devtools) MCP server which is actively maintained._** ## Features @@ -40,16 +38,10 @@ An MCP (Model Context Protocol) server that provides LLMs with efficient access ## Installation -```bash -npx -y mcp-package-docs -``` - -### Installing via Smithery - -To install Package Docs for Claude Desktop automatically via [Smithery](https://smithery.ai/server/mcp-package-docs): +Note: I do not recommend using `npx -y` to run your MCP servers in production as you're esentially trusting whatever package you're downloading off the internet at that moment in time. I highly recommend cloning the repository locally or building into a container image. ```bash -npx -y @smithery/cli install mcp-package-docs --client claude +npx -y mcp-package-docs ``` ## Usage
src/package-docs-server.ts+56 −21 modified@@ -7,7 +7,7 @@ import { McpError, } from "@modelcontextprotocol/sdk/types.js" import { getToolDefinitions } from "./tool-handlers.js" -import { exec } from "child_process" +import { execFile } from "child_process" import { promisify } from "util" import axios from "axios" import { fileURLToPath } from "url" @@ -27,7 +27,47 @@ const packageJson = JSON.parse( readFileSync(join(__dirname, "..", "package.json"), "utf-8"), ) -const execAsync = promisify(exec) +const execFileAsync = promisify(execFile) + +/** + * Sanitise input to prevent command injection + */ +function sanitiseInput(input: string): string { + // Remove shell metacharacters and limit to alphanumeric, dots, hyphens, underscores, and forward slashes + return input.replace(/[^a-zA-Z0-9.\-_/]/g, '') +} + +/** + * Safely execute go doc command using execFile + */ +async function safeGoDoc(packageName: string, symbol?: string): Promise<{ stdout: string }> { + const sanitisedPackage = sanitiseInput(packageName) + const args = ['doc'] + + if (symbol) { + const sanitisedSymbol = sanitiseInput(symbol) + args.push(`${sanitisedPackage}.${sanitisedSymbol}`) + } else { + args.push(sanitisedPackage) + } + + return await execFileAsync('go', args) +} + +/** + * Safely execute go list command using execFile + */ +async function safeGoList(packageName: string): Promise<{ stdout: string }> { + const sanitisedPackage = sanitiseInput(packageName) + return await execFileAsync('go', ['list', '-f', '{{.Dir}}', sanitisedPackage]) +} + +/** + * Safely execute python command using execFile + */ +async function safePythonExec(code: string): Promise<{ stdout: string }> { + return await execFileAsync('python3', ['-c', code]) +} export class PackageDocsServer { @@ -336,7 +376,7 @@ export class PackageDocsServer { } // Try to find the package in GOPATH - const { stdout } = await execAsync(`go list -f '{{.Dir}}' ${packageName}`) + const { stdout } = await safeGoList(packageName) return !!stdout.trim() } catch { // If the command fails, the package is likely not installed @@ -356,7 +396,7 @@ import sys spec = importlib.util.find_spec('${packageName}') print(spec is not None) ` - const { stdout } = await execAsync(`python3 -c "${pythonCode}"`) + const { stdout } = await safePythonExec(pythonCode) return stdout.trim() === "True" } catch { return false @@ -408,10 +448,7 @@ print(spec is not None) */ private async getLocalGoDoc(packageName: string, symbol?: string): Promise<DocResult> { try { - const cmd = symbol - ? `go doc ${packageName}.${symbol}` - : `go doc ${packageName}` - const { stdout } = await execAsync(cmd) + const { stdout } = await safeGoDoc(packageName, symbol) // Parse the go doc output into a structured format const lines = stdout.split("\n") @@ -467,7 +504,7 @@ import ${packageName} help(${packageName}) ` - const { stdout } = await execAsync(`python3 -c "${pythonCode}"`) + const { stdout } = await safePythonExec(pythonCode) // Parse the Python help output into a structured format const lines = stdout.split("\n") @@ -577,11 +614,12 @@ help(${packageName}) // Try to get documentation using swift-doc if available try { - const cmd = symbol - ? `swift doc generate ${packageName} --module-name ${packageName} --symbol ${symbol}` - : `swift doc generate ${packageName} --module-name ${packageName}` - - const { stdout } = await execAsync(cmd) + // Use execFileAsync for safer command execution + const args = ['doc', 'generate', packageName, '--module-name', packageName] + if (symbol) { + args.push('--symbol', symbol) + } + const { stdout } = await execFileAsync('swift', args) return { description: stdout.trim() } @@ -760,7 +798,7 @@ help(${packageName}) // First try using go doc command (works for standard library and cached modules) try { - const { stdout } = await execAsync(`go doc ${packageName}`) + const { stdout } = await safeGoDoc(packageName) docContent = this.searchUtils.parseGoDoc(stdout) docFetched = true } catch (cmdError) { @@ -794,7 +832,7 @@ help(${packageName}) if (!docFetched && packageName.includes('github.com')) { try { // Extract GitHub owner and repo from the package name - const githubMatch = packageName.match(/github\.com\/([^\/]+)\/([^\/]+)/) + const githubMatch = packageName.match(/github\.com\/([^/]+)\/([^/]+)/) if (githubMatch) { const owner = githubMatch[1] const repo = githubMatch[2] @@ -1590,10 +1628,7 @@ help(${packageName}) try { // First try using go doc command (works for standard library and cached modules) - const cmd = symbol - ? `go doc ${packageName}.${symbol}` - : `go doc ${packageName}` - const { stdout } = await execAsync(cmd) + const { stdout } = await safeGoDoc(packageName, symbol) // Parse the output into a structured format const lines = stdout.split("\n") @@ -1662,7 +1697,7 @@ import "${packageName}" if (packageName.includes('github.com')) { try { // Extract GitHub owner and repo from the package name - const githubMatch = packageName.match(/github\.com\/([^\/]+)\/([^\/]+)/) + const githubMatch = packageName.match(/github\.com\/([^/]+)\/([^/]+)/) if (githubMatch) { const owner = githubMatch[1] const repo = githubMatch[2]
tsconfig.json+5 −3 modified@@ -1,14 +1,16 @@ { "compilerOptions": { "target": "ES2022", - "module": "Node16", - "moduleResolution": "Node16", + "module": "node16", + "moduleResolution": "node16", + "lib": ["ES2022"], "outDir": "./build", "rootDir": "./src", "strict": true, "esModuleInterop": true, "skipLibCheck": true, - "forceConsistentCasingInFileNames": true + "forceConsistentCasingInFileNames": true, + "allowSyntheticDefaultImports": true }, "include": ["src/**/*"], "exclude": ["node_modules"]
445f56b9abf7ba3a4b4418d9Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
11- github.com/advisories/GHSA-3q26-f695-pp76nvdADVISORY
- github.com/advisories/GHSA-5w57-2ccq-8w95nvdADVISORY
- github.com/advisories/GHSA-gjv4-ghm7-q58qnvdADVISORY
- github.com/advisories/GHSA-vf9j-h32g-2764ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-54073ghsaADVISORY
- equixly.com/blog/2025/03/29/mcp-server-new-security-nightmarenvdWEB
- github.com/sammcj/mcp-package-docs/commit/cb4ad49615275379fd6f2f1cf1ec4731eec56eb9nvdWEB
- github.com/sammcj/mcp-package-docs/releases/tag/v0.1.27nvdWEB
- github.com/sammcj/mcp-package-docs/releases/tag/v0.1.28nvdWEB
- github.com/sammcj/mcp-package-docs/security/advisories/GHSA-vf9j-h32g-2764nvdWEB
- invariantlabs.ai/blog/mcp-github-vulnerabilitynvdWEB
News mentions
0No linked articles in our index yet.