VYPR
High severity7.5OSV Advisory· Published Sep 4, 2025· Updated Apr 15, 2026

CVE-2025-58358

CVE-2025-58358

Description

Markdownify is a Model Context Protocol server for converting almost anything to Markdown. Versions below 0.0.2 contain a command injection vulnerability, 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.). This issue is fixed in version 0.0.2.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
mcp-markdownify-servernpm
< 0.0.20.0.2

Affected products

1

Patches

1
a31204de058b

Merge commit from fork

https://github.com/zcaceres/markdownify-mcpZach CaceresSep 2, 2025via ghsa
3 files changed · +41 22
  • src/Markdownify.ts+24 19 modified
    @@ -1,11 +1,12 @@
    -import { exec } from "child_process";
    +import { execFile } from "child_process";
     import { promisify } from "util";
     import path from "path";
     import fs from "fs";
     import os from "os";
     import { fileURLToPath } from "url";
    +import { expandHome } from "./utils.js";
     
    -const execAsync = promisify(exec);
    +const execFileAsync = promisify(execFile);
     
     const __filename = fileURLToPath(import.meta.url);
     const __dirname = path.dirname(__filename);
    @@ -23,18 +24,24 @@ export class Markdownify {
       ): Promise<string> {
         const venvPath = path.join(projectRoot, ".venv");
         const markitdownPath = path.join(
    -      venvPath, 
    -      process.platform === 'win32' ? 'Scripts' : 'bin', 
    -      `markitdown${process.platform === 'win32' ? '.exe' : ''}`
    +      venvPath,
    +      process.platform === "win32" ? "Scripts" : "bin",
    +      `markitdown${process.platform === "win32" ? ".exe" : ""}`,
         );
     
         if (!fs.existsSync(markitdownPath)) {
           throw new Error("markitdown executable not found");
         }
     
    -    const { stdout, stderr } = await execAsync(
    -      `${uvPath} run ${markitdownPath} "${filePath}"`,
    -    );
    +    // Expand tilde in uvPath if present
    +    const expandedUvPath = expandHome(uvPath);
    +
    +    // Use execFile to prevent command injection
    +    const { stdout, stderr } = await execFileAsync(expandedUvPath, [
    +      "run",
    +      markitdownPath,
    +      filePath,
    +    ]);
     
         if (stderr) {
           throw new Error(`Error executing command: ${stderr}`);
    @@ -43,7 +50,10 @@ export class Markdownify {
         return stdout;
       }
     
    -  private static async saveToTempFile(content: string | Buffer, suggestedExtension?: string | null): Promise<string> {
    +  private static async saveToTempFile(
    +    content: string | Buffer,
    +    suggestedExtension?: string | null,
    +  ): Promise<string> {
         let outputExtension = "md";
         if (suggestedExtension != null) {
           outputExtension = suggestedExtension;
    @@ -60,13 +70,6 @@ export class Markdownify {
       private static normalizePath(p: string): string {
         return path.normalize(p);
       }
    -  
    -  private static expandHome(filepath: string): string {
    -    if (filepath.startsWith('~/') || filepath === '~') {
    -      return path.join(os.homedir(), filepath.slice(1));
    -    }
    -    return filepath;
    -  }
     
       static async toMarkdown({
         filePath,
    @@ -126,14 +129,16 @@ export class Markdownify {
         filePath: string;
       }): Promise<MarkdownResult> {
         // Check file type is *.md or *.markdown
    -    const normPath = this.normalizePath(path.resolve(this.expandHome(filePath)));
    +    const normPath = this.normalizePath(path.resolve(expandHome(filePath)));
         const markdownExt = [".md", ".markdown"];
    -    if (!markdownExt.includes(path.extname(normPath))){
    +    if (!markdownExt.includes(path.extname(normPath))) {
           throw new Error("Required file is not a Markdown file.");
         }
     
         if (process.env?.MD_SHARE_DIR) {
    -      const allowedShareDir = this.normalizePath(path.resolve(this.expandHome(process.env.MD_SHARE_DIR)));
    +      const allowedShareDir = this.normalizePath(
    +        path.resolve(expandHome(process.env.MD_SHARE_DIR)),
    +      );
           if (!normPath.startsWith(allowedShareDir)) {
             throw new Error(`Only files in ${allowedShareDir} are allowed.`);
           }
    
  • src/utils.ts+9 0 added
    @@ -0,0 +1,9 @@
    +import path from "path";
    +import os from "os";
    +
    +export function expandHome(filepath: string): string {
    +  if (filepath.startsWith("~/") || filepath === "~") {
    +    return path.join(os.homedir(), filepath.slice(1));
    +  }
    +  return filepath;
    +}
    
  • src/UVX.ts+8 3 modified
    @@ -1,6 +1,7 @@
    -import { exec } from "child_process";
    +import { execFile } from "child_process";
     import { promisify } from "util";
    -const execAsync = promisify(exec);
    +import { expandHome } from "./utils.js";
    +const execFileAsync = promisify(execFile);
     
     export default class UVX {
       uvxPath: string;
    @@ -33,7 +34,11 @@ export default class UVX {
       async installDeps() {
         // This is a hack to make sure that markitdown is installed before it's called in the OCRProcessor
         try {
    -      await execAsync(`${this.uvxPath} markitdown example.pdf`);
    +      // Expand tilde in uvxPath if present
    +      const expandedUvxPath = expandHome(this.uvxPath);
    +
    +      // Use execFile to prevent command injection
    +      await execFileAsync(expandedUvxPath, ["markitdown", "example.pdf"]);
         } catch {
           console.log("UVX markitdown should be ready now");
         }
    

Vulnerability 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

4

News mentions

0

No linked articles in our index yet.