T09 · Insecure Skill Coding Practices
Error
- Location
- index.js:214
- Finding
- Arbitrary Shell Command Injection Through Bundle Content## Vulnerability Details **File Location**: `index.js`, lines 214–220 **Vulnerability Type**: OS command injection through unsafe shell-command construction **Risk Level**: High ### Vulnerable Code ```javascript const { execSync } = require('child_process'); const data = fs.readFileSync(filePath, 'utf8'); try { const result = execSync('curl -s -X POST ' + EVOMAP_API + ' -H "Content-Type: application/json" -d \'' + data + '\'', { encoding: 'utf8' }); const response = JSON.parse(result); ``` ### Technical Analysis `publishBundle()` reads the contents of a user-selected bundle file and directly concatenates them into a command passed to `child_process.execSync()`. Because `execSync()` executes the constructed string through a shell, shell metacharacters in `data` are interpreted as command syntax. The request body is enclosed in single quotes, but bundle content is not escaped. A single quote in any attacker-controlled JSON string can terminate the quoted `curl` argument. The attacker can then append shell commands and use further syntax, such as a comment marker, to neutralize the remainder of the generated command. Apostrophes are valid JSON string content, so the expected input format does not prevent this condition. Parsing and rewriting by `fixBundle()` do not provide shell escaping. Attacker-controlled fields such as summaries, signals, strategies, and other retained properties can therefore carry the injection payload into the subsequent publishing operation. ### Attack Path 1. An attacker creates or modifies an EvoMap bundle containing shell syntax in a JSON string field. 2. The victim obtains that bundle and invokes either: - `node index.js publish <bundle.json>`, or - `node index.js publish-all <directory>`. 3. `fixBundle()` parses and rewrites the bundle but preserves attacker-controlled data fields. 4. `publishBundle()` reads the resulting file as raw text. 5. The raw text is ...[truncated 1135 chars]
- Remediation
- ## Remediation Suggestions 1. Eliminate shell execution from the publication path. Use Node.js `fetch()` or `https.request()` and pass the bundle as the HTTP request body: ```javascript async function publishBundle(filePath) { const data = fs.readFileSync(filePath, 'utf8'); const parsed = JSON.parse(data); const response = await fetch(EVOMAP_API, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(parsed) }); if (!response.ok) { throw new Error(`Publish failed with HTTP ${response.status}`); } return await response.json(); } ``` 2. If invoking `curl` is unavoidable, use `execFileSync()` or `spawn()` with a separate argument array and shell processing explicitly disabled. Do not construct a command string: ```javascript const { execFileSync } = require('child_process'); const result = execFileSync( 'curl', [ '-s', '-X', 'POST', EVOMAP_API, '-H', 'Content-Type: application/json', '--data-binary', data ], { encoding: 'utf8', shell: false } ); ``` 3. Parse the file as JSON and validate its schema before publication. This is defense in depth and must not replace removal of shell interpolation. 4. Add regression tests containing apostrophes and shell metacharacters in every user-controlled string field. Verify that these values are transmitted literally and never executed. 5. Propagate publication failures safely and avoid printing sensitive server responses or command details to logs.
