T09 · Insecure Skill Coding Practices
- Location
swiggy-cli.js:18- Finding
Shell Command Injection Through User-Controlled CLI Arguments
- Content
View full analysis
Vulnerability Details
File Location:
swiggy-cli.js, lines 18–28
Vulnerability Type: OS command injection caused by unsafe shell command construction
Risk Level: HighVulnerable Code
js function exec(cmd) { try { return execSync(cmd, { encoding: 'utf8', stdio: 'pipe' }); } catch (err) { console.error('Error:', err.message); if (err.stderr) console.error(err.stderr); process.exit(1); } } function callMCP(server, tool, args = {}) { const argsJson = JSON.stringify(args); const cmd = `mcporter call --server "${MCP_SERVERS[server]}" --tool "${tool}" --args '${argsJson}'`; return exec(cmd); }Technical Analysis
callMCP()serializes MCP arguments as JSON and embeds the resulting string inside a shell command enclosed by single quotes. It then passes the command string toexecSync(), which executes it through a shell.JSON serialization is not shell escaping. In particular,
JSON.stringify()does not escape shell single quotes. A single quote in any attacker-controlled MCP argument can terminate the quoted--argsvalue. The attacker can then append shell operators and arbitrary commands.Multiple CLI inputs flow into this sink, including search queries, locations, delivery addresses, item and restaurant identifiers, dates, and times. The vulnerability is therefore reachable through non-transactional operations such as restaurant searches and does not require bypassing the
--confirmorder safeguard.Attack Path
- An attacker supplies a crafted value through a forwarded CLI argument, such as a restaurant search query.
- The CLI includes that value in the MCP argument object.
JSON.stringify()produces valid JSON but leaves the shell-relevant single quote intact.callMCP()places the JSON inside a single-quoted shell command.- The crafted quote closes the
--argsshell argument. - Shell syntax following the quote is ...[truncated 1188 chars]
- Remediation
View remediation
Remediation Suggestions
Eliminate shell command construction entirely. Invoke
mcporterwith an argument array and explicitly disable shell processing:js const { execFileSync } = require('child_process'); function callMCP(server, tool, args = {}) { return execFileSync('mcporter', [ 'call', '--server', MCP_SERVERS[server], '--tool', tool, '--args', JSON.stringify(args) ], { encoding: 'utf8', stdio: 'pipe', shell: false }); }Apply the following additional hardening:
- Validate structured identifiers against strict allowlists, such as expected character sets and maximum lengths.
- Validate quantities and guest counts as bounded positive integers and reject
NaN, zero, negative, or excessive values. - Validate dates and times using strict parsers rather than forwarding arbitrary strings.
- Limit the length of free-form queries, locations, and addresses.
- Do not attempt to fix this solely with manual shell escaping; argument-array APIs are safer and less error-prone.
- Add regression tests using apostrophes, quotes, command substitutions, semicolons, newlines, pipes, redirections, and other shell metacharacters.
- Run the CLI with the minimum filesystem and credential access required for its intended function.
