T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/news-digest.mjs:75
- Finding
- Shell Command Injection in News Orchestration<![CDATA[ ## Vulnerability Details **File Location**: `scripts/news-digest.mjs`, lines 75–85 and 108–113 **Vulnerability Type**: OS command injection through shell command construction **Risk Level**: High ### Vulnerable Code ```js const envStr = [ process.env.PERPLEXITY_API_KEY ? `PERPLEXITY_API_KEY=${process.env.PERPLEXITY_API_KEY}` : '', process.env.PPIO_API_KEY ? `PPIO_API_KEY=${process.env.PPIO_API_KEY}` : '', process.env.HTTPS_PROXY ? `HTTPS_PROXY=${process.env.HTTPS_PROXY}` : '', ].filter(Boolean).join(' '); const insightFlag = noInsight ? '--no-insight' : ''; const cmd = `${envStr} node "${fetchScript}" --topic "${topic}" --count ${count} --category ${category} --date "${dateStr}" --output json ${insightFlag}`; const raw = execSync(cmd, { timeout: 120000, env: process.env }).toString().trim(); ``` ```js const sendScript = join(__dir, 'send-card.mjs'); const sectionsJson = JSON.stringify(sections).replace(/'/g, "'\\''"); const userFlag = targetUser ? `--target-user "${targetUser}"` : ''; const dryFlag = dryRun ? '--dry-run' : ''; const sendCmd = `FEISHU_APP_ID=${process.env.FEISHU_APP_ID} FEISHU_APP_SECRET=${process.env.FEISHU_APP_SECRET} TARGET_USER_ID=${process.env.TARGET_USER_ID || ''} node "${sendScript}" --title "${cardTitle}" --subtitle "${subtitle}" --json '${sectionsJson}' ${userFlag} ${dryFlag}`; const result = execSync(sendCmd, { timeout: 30000, env: process.env }).toString(); ``` ### Technical Analysis The script constructs command strings by directly interpolating command-line arguments and environment variables, then passes those strings to `execSync()`. When `execSync()` receives a string, Node.js invokes a shell to interpret it. Several interpolated values are attacker-influenced: - `topic` originates from `--topics`. - `category` originates from `--categories` and is unquoted. - `dateStr` originates from `--date`. - `cardTitle` can originate from `--title`. - `targetUser` originates from `--target-user`. - API keys, proxy s ...[truncated 2082 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Replace command-string execution with argument-array APIs that do not invoke a shell: ```js import { execFileSync } from 'child_process'; const raw = execFileSync( process.execPath, [ fetchScript, '--topic', topic, '--count', String(count), '--category', category, '--date', dateStr, '--output', 'json', ...(noInsight ? ['--no-insight'] : []), ], { timeout: 120000, env: process.env, encoding: 'utf8', } ).trim(); ``` 2. Invoke `send-card.mjs` in the same manner, passing every option as a separate array element and using `shell: false`. 3. Pass credentials only through the `env` option. Do not insert them into command text. 4. Restrict `category` to the documented allowlist: `AI`, `GEO`, `SPORT`, `BIZ`, or `CUSTOM`. 5. Validate counts as bounded positive integers and validate dates against an expected date format. 6. Apply reasonable length limits to topics, titles, and user identifiers. 7. Add regression tests using quotes, semicolons, backticks, newlines, and `$()` expressions to confirm that values remain literal arguments. ]]>
