T09 · Insecure Skill Coding Practices
- Location
scripts/export-to-notes.js:137- Finding
Command Injection in Bear Export Through Untrusted Post Content and Arguments
- Content
View full analysis
{ markdown += `## ${i + 1}. ${p.author || 'Unknown'}\n\n`; markdown += `${p.text || ''}\n\n`; markdown += `**Engagement:** ${extractEngagement(p)} | `; markdown += `[View](${p.url || '#'})\n\n`; markdown += `---\n\n`; }); const tagList = tags?.split(',') || ['skroller', 'research']; const tagArgs = tagList.map(t => `--tag "${t}"`).join(' '); const escaped = markdown.replace(/"/g, '\\"').replace(/\$/g, '\\$'); const command = `echo "${escaped}" | grizzly create --title "${title}" ${tagArgs}`; execSync(command, { stdio: 'inherit' }); ``` ### Technical Analysis The Bear exporter constructs a shell command by concatenating values from several untrusted sources: - Scraped post text, author names, and URLs - The generated or user-supplied title - User-supplied tags The attempted escaping only handles double quotes and dollar signs in `markdown`. It does not safely handle shell constructs such as backticks, command separators, redirections, newlines, or other shell metacharacters. The `title` and individual tag values are not escaped at all. Because `execSync()` receives a single command string, Node.js invokes a shell to interpret it. As a result, attacker-controlled social-media content can become executable shell syntax rather than remaining inert note content. ### Attack Path 1. An attacker publishes a social-media post containing a shell payload in its text, author field, or another extracted value. 2. The user runs `scripts/skroller.js` and collects the attacker-controlled post. 3. The resulting JSON is passed to `scripts/export-to-notes.js --app bear`. 4. The malicious content is inserted into `markdown`. 5. The exporter concatenates that content into the `echo ... | grizzly ...` shell command. 6. `execSync() ...[truncated 782 chars]- Remediation
View remediation
