VYPR
Medium severity5.3NVD Advisory· Published Apr 5, 2026· Updated Apr 29, 2026

CVE-2026-5603

CVE-2026-5603

Description

A vulnerability was identified in elgentos magento2-dev-mcp up to 1.0.2. The affected element is the function executeMagerun2Command of the file src/index.ts. Such manipulation leads to os command injection. An attack has to be approached locally. The exploit is publicly available and might be used. The name of the patch is aa1ffcc0aea1b212c69787391783af27df15ae9d. A patch should be applied to remediate this issue.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
@elgentos/magento2-dev-mcpnpm
<= 1.0.2

Affected products

1

Patches

1
aa1ffcc0aea1

Merge pull request #5 from elgentos/copilot/fix-command-injection-vulnerability

https://github.com/elgentos/magento2-dev-mcpPeter Jaap BlaakmeerMar 12, 2026via ghsa
2 files changed · +141 221
  • package-lock.json+7 4 modified
    @@ -1,17 +1,20 @@
     {
    -  "name": "magento2-dev-mcp-server",
    -  "version": "1.0.0",
    +  "name": "@elgentos/magento2-dev-mcp",
    +  "version": "1.0.2",
       "lockfileVersion": 3,
       "requires": true,
       "packages": {
         "": {
    -      "name": "magento2-dev-mcp-server",
    -      "version": "1.0.0",
    +      "name": "@elgentos/magento2-dev-mcp",
    +      "version": "1.0.2",
           "license": "MIT",
           "dependencies": {
             "@modelcontextprotocol/sdk": "^1.18.2",
             "zod": "^3.23.8"
           },
    +      "bin": {
    +        "magento2-dev-mcp": "dist/cli.js"
    +      },
           "devDependencies": {
             "@types/node": "^20.0.0",
             "typescript": "^5.0.0"
    
  • src/index.ts+134 217 modified
    @@ -3,13 +3,14 @@
     import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
     import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
     import { z } from "zod";
    -import { exec } from "child_process";
    +import { exec, execFile } from "child_process";
     import { promisify } from "util";
     import { executePhpScript } from "./php-executor.js";
     import { formatPluginAnalysis } from "./plugin-list-formatter.js";
    -import { detectDockerEnvironment } from "./docker-env.js";
    +import { detectDockerEnvironment, shellQuote } from "./docker-env.js";
     
     const execAsync = promisify(exec);
    +const execFileAsync = promisify(execFile);
     
     const magerunBin = process.env.MAGERUN2_COMMAND || 'magerun2';
     const dockerEnv = detectDockerEnvironment(process.cwd());
    @@ -30,11 +31,14 @@ const server = new McpServer({
     
     /**
      * Helper function to execute magerun2 commands with consistent error handling.
    - * Accepts the subcommand (everything after the binary name, e.g. "cache:clean --all").
    + * Accepts the subcommand arguments as an array (e.g. ["cache:clean", "--all"]).
      * When a Docker environment is detected, commands are routed through the container
      * with a local fallback. The binary name can be configured via MAGERUN2_COMMAND env var.
    + *
    + * Uses execFile for local execution to prevent shell injection from user-supplied arguments.
    + * Docker wrapper commands are built using shellQuote to safely embed arguments.
      */
    -async function executeMagerun2Command(subcommand: string, parseJson: boolean = false): Promise<{
    +async function executeMagerun2Command(args: string[], parseJson: boolean = false): Promise<{
       success: true;
       data: any;
       rawOutput: string;
    @@ -43,49 +47,59 @@ async function executeMagerun2Command(subcommand: string, parseJson: boolean = f
       error: string;
       isError: true;
     }> {
    -  const fullCommand = `${magerunBin} ${subcommand}`;
    -  const commands: string[] = [];
    -
    -  if (dockerEnv) {
    -    commands.push(...dockerEnv.wrapCommand(fullCommand));
    -  }
    -  commands.push(fullCommand); // local fallback always included
    -
    +  const execOptions = { cwd: process.cwd(), timeout: 30000 };
       const errors: string[] = [];
     
    -  for (const command of commands) {
    -    try {
    -      const { stdout, stderr } = await execAsync(command, {
    -        cwd: process.cwd(),
    -        timeout: 30000 // 30 second timeout
    -      });
    -
    -      if (stderr && stderr.trim()) {
    -        console.error("magerun2 stderr:", stderr);
    +  /**
    +   * Process stdout/stderr from a successful execution.
    +   */
    +  function processOutput(stdout: string, stderr: string | undefined) {
    +    if (stderr && String(stderr).trim()) {
    +      console.error("magerun2 stderr:", stderr);
    +    }
    +    if (parseJson) {
    +      try {
    +        return { success: true as const, data: JSON.parse(stdout), rawOutput: stdout };
    +      } catch (parseError) {
    +        return {
    +          success: false as const,
    +          error: `Error parsing magerun2 JSON output: ${parseError}\n\nRaw output:\n${stdout}`,
    +          isError: true as const
    +        };
           }
    +    }
    +    return { success: true as const, data: stdout.trim(), rawOutput: stdout };
    +  }
     
    -      if (parseJson) {
    -        try {
    -          return { success: true, data: JSON.parse(stdout), rawOutput: stdout };
    -        } catch (parseError) {
    -          return {
    -            success: false,
    -            error: `Error parsing magerun2 JSON output: ${parseError}\n\nRaw output:\n${stdout}`,
    -            isError: true
    -          };
    -        }
    +  // Try Docker environments first (shell-based; args are safely quoted)
    +  if (dockerEnv) {
    +    const safeInnerCmd = [magerunBin, ...args].map(shellQuote).join(' ');
    +    const dockerCommands = dockerEnv.wrapCommand(safeInnerCmd);
    +
    +    for (const command of dockerCommands) {
    +      try {
    +        const { stdout, stderr } = await execAsync(command, execOptions);
    +        return processOutput(stdout, stderr);
    +      } catch (error) {
    +        const msg = error instanceof Error ? error.message : String(error);
    +        console.error(`magerun2 command failed: ${command}\n  ${msg}`);
    +        errors.push(`[${command}] ${msg}`);
    +        continue;
           }
    -
    -      return { success: true, data: stdout.trim(), rawOutput: stdout };
    -
    -    } catch (error) {
    -      const msg = error instanceof Error ? error.message : String(error);
    -      console.error(`magerun2 command failed: ${command}\n  ${msg}`);
    -      errors.push(`[${command}] ${msg}`);
    -      continue;
         }
       }
     
    +  // Local execution: use execFile to avoid shell interpretation of user arguments
    +  try {
    +    const { stdout, stderr } = await execFileAsync(magerunBin, args, execOptions);
    +    return processOutput(stdout, stderr as string | undefined);
    +  } catch (error) {
    +    const msg = error instanceof Error ? error.message : String(error);
    +    const displayCmd = `${magerunBin} ${args.join(' ')}`;
    +    console.error(`magerun2 command failed: ${displayCmd}\n  ${msg}`);
    +    errors.push(`[${displayCmd}] ${msg}`);
    +  }
    +
       // All commands failed — build a helpful error message
       const lastError = errors[errors.length - 1] ?? '';
       const allNotFound = errors.every(e =>
    @@ -147,8 +161,7 @@ server.registerTool(
         }
       },
       async ({ scope = "global" }) => {
    -    const command = `dev:di:preferences:list --format=json ${scope}`;
    -    const result = await executeMagerun2Command(command, true);
    +    const result = await executeMagerun2Command(['dev:di:preferences:list', '--format=json', scope], true);
     
         if (!result.success) {
           return {
    @@ -188,9 +201,8 @@ server.registerTool(
         }
       },
       async ({ types }) => {
    -    const cacheTypesArg = types && types.length > 0 ? types.join(' ') : '';
    -    const command = `cache:clean ${cacheTypesArg}`.trim();
    -    const result = await executeMagerun2Command(command);
    +    const args: string[] = ['cache:clean', ...(types ?? [])];
    +    const result = await executeMagerun2Command(args);
     
         if (!result.success) {
           return {
    @@ -228,9 +240,8 @@ server.registerTool(
         }
       },
       async ({ types }) => {
    -    const cacheTypesArg = types && types.length > 0 ? types.join(' ') : '';
    -    const command = `cache:flush ${cacheTypesArg}`.trim();
    -    const result = await executeMagerun2Command(command);
    +    const args: string[] = ['cache:flush', ...(types ?? [])];
    +    const result = await executeMagerun2Command(args);
     
         if (!result.success) {
           return {
    @@ -268,8 +279,7 @@ server.registerTool(
         }
       },
       async ({ types }) => {
    -    const command = `cache:enable ${types.join(' ')}`;
    -    const result = await executeMagerun2Command(command);
    +    const result = await executeMagerun2Command(['cache:enable', ...types]);
     
         if (!result.success) {
           return {
    @@ -307,8 +317,7 @@ server.registerTool(
         }
       },
       async ({ types }) => {
    -    const command = `cache:disable ${types.join(' ')}`;
    -    const result = await executeMagerun2Command(command);
    +    const result = await executeMagerun2Command(['cache:disable', ...types]);
     
         if (!result.success) {
           return {
    @@ -342,8 +351,7 @@ server.registerTool(
         inputSchema: {}
       },
       async () => {
    -    const command = `cache:status`;
    -    const result = await executeMagerun2Command(command);
    +    const result = await executeMagerun2Command(['cache:status']);
     
         if (!result.success) {
           return {
    @@ -383,9 +391,10 @@ server.registerTool(
         }
       },
       async ({ key, type }) => {
    -    const typeArg = type ? `--type=${type}` : '';
    -    const command = `cache:view ${typeArg} "${key}"`.trim();
    -    const result = await executeMagerun2Command(command);
    +    const args: string[] = ['cache:view'];
    +    if (type) args.push('--type', type);
    +    args.push(key);
    +    const result = await executeMagerun2Command(args);
     
         if (!result.success) {
           return {
    @@ -429,15 +438,11 @@ server.registerTool(
         }
       },
       async ({ format = "table", enabled, disabled }) => {
    -    let command = `dev:module:list --format=${format}`;
    +    const args: string[] = ['dev:module:list', `--format=${format}`];
    +    if (enabled) args.push('--only-enabled');
    +    else if (disabled) args.push('--only-disabled');
     
    -    if (enabled) {
    -      command += ' --only-enabled';
    -    } else if (disabled) {
    -      command += ' --only-disabled';
    -    }
    -
    -    const result = await executeMagerun2Command(command, format === "json");
    +    const result = await executeMagerun2Command(args, format === "json");
     
         if (!result.success) {
           return {
    @@ -482,13 +487,10 @@ server.registerTool(
         }
       },
       async ({ format = "table", event }) => {
    -    let command = `dev:module:observer:list --format=${format}`;
    -
    -    if (event) {
    -      command += ` "${event}"`;
    -    }
    +    const args: string[] = ['dev:module:observer:list', `--format=${format}`];
    +    if (event) args.push(event);
     
    -    const result = await executeMagerun2Command(command, format === "json");
    +    const result = await executeMagerun2Command(args, format === "json");
     
         if (!result.success) {
           return {
    @@ -590,65 +592,24 @@ server.registerTool(
         authorEmail,
         description
       }) => {
    -    let command = `dev:module:create "${vendorNamespace}" "${moduleName}"`;
    -
    -    if (minimal) {
    -      command += ` --minimal`;
    -    }
    -
    -    if (addBlocks) {
    -      command += ` --add-blocks`;
    -    }
    -
    -    if (addHelpers) {
    -      command += ` --add-helpers`;
    -    }
    -
    -    if (addModels) {
    -      command += ` --add-models`;
    -    }
    -
    -    if (addSetup) {
    -      command += ` --add-setup`;
    -    }
    -
    -    if (addAll) {
    -      command += ` --add-all`;
    -    }
    -
    -    if (enable) {
    -      command += ` --enable`;
    -    }
    -
    -    if (modman) {
    -      command += ` --modman`;
    -    }
    -
    -    if (addReadme) {
    -      command += ` --add-readme`;
    -    }
    -
    -    if (addComposer) {
    -      command += ` --add-composer`;
    -    }
    -
    -    if (addStrictTypes) {
    -      command += ` --add-strict-types`;
    -    }
    -
    -    if (authorName) {
    -      command += ` --author-name="${authorName}"`;
    -    }
    -
    -    if (authorEmail) {
    -      command += ` --author-email="${authorEmail}"`;
    -    }
    -
    -    if (description) {
    -      command += ` --description="${description}"`;
    -    }
    -
    -    const result = await executeMagerun2Command(command);
    +    const args: string[] = ['dev:module:create', vendorNamespace, moduleName];
    +
    +    if (minimal) args.push('--minimal');
    +    if (addBlocks) args.push('--add-blocks');
    +    if (addHelpers) args.push('--add-helpers');
    +    if (addModels) args.push('--add-models');
    +    if (addSetup) args.push('--add-setup');
    +    if (addAll) args.push('--add-all');
    +    if (enable) args.push('--enable');
    +    if (modman) args.push('--modman');
    +    if (addReadme) args.push('--add-readme');
    +    if (addComposer) args.push('--add-composer');
    +    if (addStrictTypes) args.push('--add-strict-types');
    +    if (authorName) args.push('--author-name', authorName);
    +    if (authorEmail) args.push('--author-email', authorEmail);
    +    if (description) args.push('--description', description);
    +
    +    const result = await executeMagerun2Command(args);
     
         if (!result.success) {
           return {
    @@ -686,8 +647,7 @@ server.registerTool(
         }
       },
       async ({ format = "table" }) => {
    -    const command = `sys:info --format=${format}`;
    -    const result = await executeMagerun2Command(command, format === "json");
    +    const result = await executeMagerun2Command(['sys:info', `--format=${format}`], format === "json");
     
         if (!result.success) {
           return {
    @@ -725,8 +685,7 @@ server.registerTool(
         inputSchema: {}
       },
       async () => {
    -    const command = `sys:check`;
    -    const result = await executeMagerun2Command(command);
    +    const result = await executeMagerun2Command(['sys:check']);
     
         if (!result.success) {
           return {
    @@ -770,21 +729,12 @@ server.registerTool(
         }
       },
       async ({ path, scope, scopeId }) => {
    -    let command = `config:show`;
    +    const args: string[] = ['config:show'];
    +    if (path) args.push(path);
    +    if (scope) args.push('--scope', scope);
    +    if (scopeId) args.push('--scope-id', scopeId);
     
    -    if (path) {
    -      command += ` "${path}"`;
    -    }
    -
    -    if (scope) {
    -      command += ` --scope="${scope}"`;
    -    }
    -
    -    if (scopeId) {
    -      command += ` --scope-id="${scopeId}"`;
    -    }
    -
    -    const result = await executeMagerun2Command(command);
    +    const result = await executeMagerun2Command(args);
     
         if (!result.success) {
           return {
    @@ -832,21 +782,12 @@ server.registerTool(
         }
       },
       async ({ path, value, scope, scopeId, encrypt }) => {
    -    let command = `config:set "${path}" "${value}"`;
    -
    -    if (scope) {
    -      command += ` --scope="${scope}"`;
    -    }
    -
    -    if (scopeId) {
    -      command += ` --scope-id="${scopeId}"`;
    -    }
    -
    -    if (encrypt) {
    -      command += ` --encrypt`;
    -    }
    +    const args: string[] = ['config:set', path, value];
    +    if (scope) args.push('--scope', scope);
    +    if (scopeId) args.push('--scope-id', scopeId);
    +    if (encrypt) args.push('--encrypt');
     
    -    const result = await executeMagerun2Command(command);
    +    const result = await executeMagerun2Command(args);
     
         if (!result.success) {
           return {
    @@ -886,13 +827,10 @@ server.registerTool(
         }
       },
       async ({ path, storeId }) => {
    -    let command = `config:store:get "${path}"`;
    +    const args: string[] = ['config:store:get', path];
    +    if (storeId) args.push('--store-id', storeId);
     
    -    if (storeId) {
    -      command += ` --store-id="${storeId}"`;
    -    }
    -
    -    const result = await executeMagerun2Command(command);
    +    const result = await executeMagerun2Command(args);
     
         if (!result.success) {
           return {
    @@ -934,13 +872,10 @@ server.registerTool(
         }
       },
       async ({ path, value, storeId }) => {
    -    let command = `config:store:set "${path}" "${value}"`;
    -
    -    if (storeId) {
    -      command += ` --store-id="${storeId}"`;
    -    }
    +    const args: string[] = ['config:store:set', path, value];
    +    if (storeId) args.push('--store-id', storeId);
     
    -    const result = await executeMagerun2Command(command);
    +    const result = await executeMagerun2Command(args);
     
         if (!result.success) {
           return {
    @@ -980,8 +915,7 @@ server.registerTool(
         }
       },
       async ({ query, format = "table" }) => {
    -    const command = `db:query --format=${format} "${query}"`;
    -    const result = await executeMagerun2Command(command, format === "json");
    +    const result = await executeMagerun2Command(['db:query', `--format=${format}`, query], format === "json");
     
         if (!result.success) {
           return {
    @@ -1023,13 +957,10 @@ server.registerTool(
         }
       },
       async ({ keepGenerated }) => {
    -    let command = `setup:upgrade`;
    +    const args: string[] = ['setup:upgrade'];
    +    if (keepGenerated) args.push('--keep-generated');
     
    -    if (keepGenerated) {
    -      command += ` --keep-generated`;
    -    }
    -
    -    const result = await executeMagerun2Command(command);
    +    const result = await executeMagerun2Command(args);
     
         if (!result.success) {
           return {
    @@ -1063,8 +994,7 @@ server.registerTool(
         inputSchema: {}
       },
       async () => {
    -    const command = `setup:di:compile`;
    -    const result = await executeMagerun2Command(command);
    +    const result = await executeMagerun2Command(['setup:di:compile']);
     
         if (!result.success) {
           return {
    @@ -1098,8 +1028,7 @@ server.registerTool(
         inputSchema: {}
       },
       async () => {
    -    const command = `setup:db:status`;
    -    const result = await executeMagerun2Command(command);
    +    const result = await executeMagerun2Command(['setup:db:status']);
     
         if (!result.success) {
           return {
    @@ -1146,25 +1075,27 @@ server.registerTool(
         }
       },
       async ({ languages, themes, jobs, force }) => {
    -    let command = `setup:static-content:deploy`;
    +    const args: string[] = ['setup:static-content:deploy'];
     
         if (languages && languages.length > 0) {
    -      command += ` ${languages.join(' ')}`;
    +      args.push(...languages);
         }
     
         if (themes && themes.length > 0) {
    -      command += ` --theme=${themes.join(' --theme=')}`;
    +      for (const theme of themes) {
    +        args.push('--theme', theme);
    +      }
         }
     
         if (jobs) {
    -      command += ` --jobs=${jobs}`;
    +      args.push('--jobs', String(jobs));
         }
     
         if (force) {
    -      command += ` --force`;
    +      args.push('--force');
         }
     
    -    const result = await executeMagerun2Command(command);
    +    const result = await executeMagerun2Command(args);
     
         if (!result.success) {
           return {
    @@ -1202,8 +1133,7 @@ server.registerTool(
         }
       },
       async ({ format = "table" }) => {
    -    const command = `sys:store:list --format=${format}`;
    -    const result = await executeMagerun2Command(command, format === "json");
    +    const result = await executeMagerun2Command(['sys:store:list', `--format=${format}`], format === "json");
     
         if (!result.success) {
           return {
    @@ -1245,8 +1175,7 @@ server.registerTool(
         }
       },
       async ({ format = "table" }) => {
    -    const command = `dev:theme:list --format=${format}`;
    -    const result = await executeMagerun2Command(command, format === "json");
    +    const result = await executeMagerun2Command(['dev:theme:list', `--format=${format}`], format === "json");
     
         if (!result.success) {
           return {
    @@ -1288,8 +1217,7 @@ server.registerTool(
         }
       },
       async ({ format = "table" }) => {
    -    const command = `sys:store:config:base-url:list --format=${format}`;
    -    const result = await executeMagerun2Command(command, format === "json");
    +    const result = await executeMagerun2Command(['sys:store:config:base-url:list', `--format=${format}`], format === "json");
     
         if (!result.success) {
           return {
    @@ -1331,8 +1259,7 @@ server.registerTool(
         }
       },
       async ({ format = "table" }) => {
    -    const command = `sys:cron:list --format=${format}`;
    -    const result = await executeMagerun2Command(command, format === "json");
    +    const result = await executeMagerun2Command(['sys:cron:list', `--format=${format}`], format === "json");
     
         if (!result.success) {
           return {
    @@ -1377,13 +1304,10 @@ server.registerTool(
         }
       },
       async ({ format = "table", storeId }) => {
    -    let command = `sys:url:list --format=${format}`;
    -
    -    if (storeId) {
    -      command += ` --store-id=${storeId}`;
    -    }
    +    const args: string[] = ['sys:url:list', `--format=${format}`];
    +    if (storeId) args.push('--store-id', storeId);
     
    -    const result = await executeMagerun2Command(command, format === "json");
    +    const result = await executeMagerun2Command(args, format === "json");
     
         if (!result.success) {
           return {
    @@ -1425,8 +1349,7 @@ server.registerTool(
         }
       },
       async ({ format = "table" }) => {
    -    const command = `sys:website:list --format=${format}`;
    -    const result = await executeMagerun2Command(command, format === "json");
    +    const result = await executeMagerun2Command(['sys:website:list', `--format=${format}`], format === "json");
     
         if (!result.success) {
           return {
    @@ -1471,17 +1394,11 @@ server.registerTool(
         }
       },
       async ({ job, group }) => {
    -    let command = `sys:cron:run`;
    -
    -    if (job) {
    -      command += ` "${job}"`;
    -    }
    -
    -    if (group) {
    -      command += ` --group="${group}"`;
    -    }
    +    const args: string[] = ['sys:cron:run'];
    +    if (job) args.push(job);
    +    if (group) args.push('--group', group);
     
    -    const result = await executeMagerun2Command(command);
    +    const result = await executeMagerun2Command(args);
     
         if (!result.success) {
           return {
    

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

8

News mentions

0

No linked articles in our index yet.