T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/export-to-notes.js:137
- Finding
- Command Injection in Bear Export Through Untrusted Post Content and Arguments<\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
- <![CDATA[ ## Remediation Suggestions - Remove shell-string construction entirely. - Invoke `grizzly` with `spawnSync()` or `execFileSync()` using a fixed executable and an argument array. - Supply the Markdown document through the child process’s standard input rather than through `echo`. - Never concatenate titles, tags, URLs, authors, or post text into a shell command. - Validate tags and other identifiers against a restrictive allowlist where practical. - Treat every field loaded from scraped JSON as untrusted, even if it originated from a public platform. - Add regression tests containing quotes, backticks, newlines, redirection characters, command substitutions, and command separators. A safer design is: ```javascript const { spawnSync } = require('child_process'); const args = ['create', '--title', title]; for (const tag of tagList) { args.push('--tag', tag); } const result = spawnSync('grizzly', args, { input: markdown, encoding: 'utf8', stdio: ['pipe', 'inherit', 'inherit'], shell: false }); if (result.error) { throw result.error; } ``` ]]>
