T09 · Insecure Skill Coding Practices
- Location
api/browser/open-url.js:18- Finding
Shell Command Injection in the URL-Opening Utility
- Content
View full analysis
hostname === host || hostname.endsWith('.' + host)) } catch { return false } } async function openUrl(url) { if (!url) throw new Error('URL is required') if (!isAllowedUrl(url)) throw new Error(`URL not allowed: ${url}`) if (process.platform === 'darwin') { await execAsync(`open "${url}"`) } else if (process.platform === 'win32') { await execAsync(`start "" "${url}"`) } else { await execAsync(`xdg-open "${url}"`) } } ``` ### Technical Analysis The function validates the parsed URL protocol and hostname, but it subsequently interpolates the original, untrusted URL string into an operating-system shell command executed through `child_process.exec`. Hostname validation does not make the complete URL safe for shell interpolation. On Unix-like systems, command substitutions such as `$()` and backticks are still evaluated inside double quotes. Consequently, a URL can have an allowed `i.beautsgo.com` hostname while containing shell metacharacters in its path, query, or fragment. For example, a conceptual input with an allowed hostname and a path containing `$(attacker-command)` would pass `isAllowedUrl()`. When embedded in the `xdg-open` or `open` command, the shell could evaluate the substitution before launching the browser. The normal `processQuery` routes currently derive URLs from bundled hospital data, which limits exposure through the primary Skill interface. However, `openUrl` is exported, and `api/browser/open-url.js:41-44` also exposes it directly through the command line. The vulnerable function therefore remains independently reachable if anot ...[truncated 1359 chars]- Remediation
View remediation
