T09 · Insecure Skill Coding Practices
- Location
scripts/x-to-notebooklm.mjs:104- Finding
OS Command Injection Through Unvalidated Command-Line Arguments and Environment Configuration
- Content
View full analysis
Vulnerability Details
File Location:
scripts/x-to-notebooklm.mjs, lines 23-24, 104, 159-162, 182-184, and 272-274
Vulnerability Type: OS command injection through shell command construction
Risk Level: HighVulnerable Code
js const NOTEBOOKLM_CLI_PATH = process.env.NOTEBOOKLM_CLI_PATH || `${process.env.HOME}/.openclaw/skills/tiangong-notebooklm-cli/scripts/notebooklm.mjs`;js const jinaUrl = `https://r.jina.ai/${url}`; const result = execCommand(`curl -s "${jinaUrl}"`, timeout);js const result = execCommand( `node "${NOTEBOOKLM_CLI_PATH}" create "${notebookName}" --json`, timeout );js const result = execCommand( `node "${NOTEBOOKLM_CLI_PATH}" source add "${tempFile}" --notebook "${notebookId}"`, timeout );js const result = execCommand( `node "${NOTEBOOKLM_CLI_PATH}" source get "${sourceId}"`, timeout );The commands are ultimately executed through:
js function execCommand(command, timeout = 30) { try { const output = execSync(command, { encoding: 'utf-8', timeout: timeout * 1000, maxBuffer: 10 * 1024 * 1024 }); return { success: true, output: output.trim() }; } catch (error) { return { success: false, error: error.message, output: error.stdout?.toString() || '', stderr: error.stderr?.toString() || '' }; } }Technical Analysis
execSync()executes a command string through a system shell. The script inserts the URL, notebook name, notebook identifier, NotebookLM CLI path, and indirectly parsed source identifier into these shell command strings without shell-safe argument handling.Placing a value inside double quotes does not make it safe for shell execution. Shell constructs such as command substitution using
$()or backticks are still evaluated inside double-quoted strings. Consequently, an att ...[truncated 2576 chars]- Remediation
View remediation
Remediation Suggestions
-
Replace
execSync()command strings withexecFileSync()orspawnSync()and pass arguments as an array:js import { execFileSync } from 'child_process'; function execProgram(program, args, timeout = 30) { try { const output = execFileSync(program, args, { encoding: 'utf-8', timeout: timeout * 1000, maxBuffer: 10 * 1024 * 1024, shell: false }); return { success: true, output: output.trim() }; } catch (error) { return { success: false, error: error.message, output: error.stdout?.toString() || '', stderr: error.stderr?.toString() || '' }; } } -
Invoke
curlwithout a shell:js execProgram('curl', ['-s', jinaUrl], timeout); -
Invoke the NotebookLM CLI with discrete arguments:
js execProgram( process.execPath, [NOTEBOOKLM_CLI_PATH, 'create', notebookName, '--json'], timeout ); -
Parse the supplied URL with the standard
URLclass. Require HTTPS and restrict hosts to the Skill's declared scope:js const parsed = new URL(url); if (parsed.protocol !== 'https:' || !['x.com', 'www.x.com', 'twitter.com', 'www.twitter.com'].includes(parsed.hostname)) { throw new Error('Only HTTPS X/Twitter URLs are supported'); } -
Validate notebook identifiers against the precise format expected by the NotebookLM CLI. Apply reasonable length limits to notebook names.
-
Validate
NOTEBOOKLM_CLI_PATHas an absolute path to a regular file. Do not pass it through a shell, even after validation. -
Constrain
--timeoutto a finite positive range to prevent unexpected process behavior or denial of service.
-
