T09 · Insecure Skill Coding Practices
- Location
google-docs-to-feishu.js:438- Finding
OS Command Injection Through Untrusted Document and CLI Values
- Content
View full analysis
Vulnerability Details
File Location:
google-docs-to-feishu.js, lines 438–440 and 517–519
Vulnerability Type: OS command injection
Risk Level: HighVulnerable Code
js const createCmd = ` openclaw tools call feishu_doc '{"action":"create","title":"${title}"${folderToken ? `,"folder_token":"${folderToken}"` : ''}${ownerOpenId ? `,"owner_open_id":"${ownerOpenId}"` : ''}}' `; try { const result = execSync(createCmd, { encoding: 'utf8' }); return JSON.parse(result); } catch (e) { return createFeishuDocByAPI(title, folderToken, ownerOpenId); }A second vulnerable command is used when writing the document:
js const writeCmd = `openclaw tools call feishu_doc '{"action":"write","doc_token":"${feishuResult.doc_token}","content":${JSON.stringify(markdown)}}'`; execSync(writeCmd, { encoding: 'utf8' });Technical Analysis
The code constructs shell command strings by directly interpolating values from several untrusted or externally controlled sources:
titleoriginates from the Google document.markdownoriginates from the Google document body.folderTokenandownerOpenIdare command-line arguments.doc_tokenis returned by an external tool.
These strings are passed to
execSync(), which invokes a shell. The JSON argument is enclosed in shell single quotes, but interpolated values are not escaped for the shell. A single quote in a document title, document content, folder token, or owner identifier can terminate the quoted argument. Shell metacharacters following it can then introduce an arbitrary command.JSON.stringify(markdown)only performs JSON serialization. It does not make the result safe for insertion into a shell command, because JSON escaping and shell escaping follow different rules.Attack Path
- An attacker creates or modifies a Google document accessible to the victim.
- The attacker places a shell-breaking payload in ...[truncated 1431 chars]
- Remediation
View remediation
Remediation Suggestions
-
Eliminate shell command construction for tool invocation.
-
Use
execFileSync()orspawnSync()with an argument array so that the operating system does not interpret document values as shell syntax. -
Construct the complete tool payload as an object and serialize it once:
js const payload = { action: 'create', title }; if (folderToken) payload.folder_token = folderToken; if (ownerOpenId) payload.owner_open_id = ownerOpenId; const result = execFileSync( 'openclaw', ['tools', 'call', 'feishu_doc', JSON.stringify(payload)], { encoding: 'utf8', shell: false } ); -
Apply the same approach to the write operation:
js const payload = { action: 'write', doc_token: feishuResult.doc_token, content: markdown }; execFileSync( 'openclaw', ['tools', 'call', 'feishu_doc', JSON.stringify(payload)], { encoding: 'utf8', shell: false } ); -
Prefer a direct, structured OpenClaw API or SDK instead of invoking a CLI.
-
Validate folder tokens, owner identifiers, and returned document tokens against their expected character sets and length limits.
-
Treat validation as defense in depth rather than as a substitute for removing shell interpretation.
-
Add regression tests containing single quotes, command substitutions, newlines, semicolons, and other shell metacharacters in document titles and content.
-
