T09 · Insecure Skill Coding Practices
- Location
- sync.js:16
- Finding
- Shell Command Injection Through Interpolated Drive Identifiers<![CDATA[ ## Vulnerability Details **File Location**: `sync.js:16-36` **Vulnerability Type**: OS command injection **Risk Level**: High ### Vulnerable Code ```js function getGWSFiles(folderId) { const query = `'${folderId}' in parents and trashed = false`; const escapedQuery = query.replace(/'/g, "'\\''"); const cmd = `SSL_CERT_FILE=/workspace/cacert.pem npx @googleworkspace/cli drive files list --params '{"q": "${escapedQuery}", "fields": "files(id, name, mimeType, shortcutDetails)"}'`; try { const res = execSync(cmd, { encoding: 'utf-8', stdio: 'pipe' }); const jsonStart = res.indexOf('{'); const cleanRes = res.substring(jsonStart); return JSON.parse(cleanRes).files || []; } catch(e) { console.error("Error fetching files from GWS:", e.message); return []; } } function downloadFile(fileId, dest) { const cmd = `SSL_CERT_FILE=/workspace/cacert.pem npx @googleworkspace/cli drive files get --params '{"fileId": "${fileId}", "alt": "media"}' --output "${dest}"`; try { execSync(cmd, { stdio: 'pipe' }); return true; } catch(e) { return false; } } ``` ### Technical Analysis The implementation constructs shell command strings by interpolating `folderId`, `fileId`, and `dest`, then passes the resulting strings to `execSync()`. String-based `execSync()` invokes a shell, so shell metacharacters and command substitutions are interpreted before the Google Workspace CLI receives its arguments. The attempted escaping in `getGWSFiles()` only transforms apostrophes. It does not provide correct shell escaping for all surrounding quoting contexts and does not neutralize constructs such as command substitution, backticks, shell expansions, double quotes, or other metacharacters. The initial folder identifier is loaded from `FILECHAT_DRIVE_FOLDER_ID`. Recursive identifiers are obtained from Google Drive metadata, including shortcut target identifiers. Although legitimate Google-generated IDs normally have a restricted f ...[truncated 1332 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions - Replace string-based `execSync()` with `execFileSync()` or `spawnSync()` and pass each argument as a separate array element. - Do not invoke a shell to set `SSL_CERT_FILE`; pass it through the child process `env` option instead. - Validate Drive IDs using a strict allowlist appropriate for Google-generated identifiers. - Use absolute, internally generated output paths rather than interpolating caller-controlled paths. - Reject identifiers that do not meet the expected format before invoking the CLI. - Avoid attempting to implement shell escaping manually. A safer pattern is: ```js const { execFileSync } = require('child_process'); const output = execFileSync( gwsBinary, [ 'drive', 'files', 'list', '--params', JSON.stringify({ q: `'${folderId}' in parents and trashed = false`, fields: 'files(id, name, mimeType, shortcutDetails)' }) ], { encoding: 'utf8', stdio: 'pipe', env: { ...process.env, SSL_CERT_FILE: '/workspace/cacert.pem' } } ); ``` ]]>
