T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/capture-rankings.mjs:332
- Finding
- Arbitrary URL Navigation in an Unsandboxed Chromium Process<![CDATA[ ## Vulnerability Details **File Location**: `scripts/capture-rankings.mjs`, lines 332–364 **Vulnerability Type**: Unrestricted browser navigation and disabled process sandbox **Risk Level**: High ### Vulnerable Code ```js const url = process.argv[2] || DEFAULT_URL; ``` ```js const browser = await puppeteer.launch({ executablePath: CHROMIUM, headless: true, args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage', '--disable-gpu'], }); ``` ```js console.log(`🌐 Loading ${url} ...`); await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 120000 }); ``` ### Technical Analysis The second command-line argument directly controls the URL passed to `page.goto()` without validating its protocol, hostname, port, resolved address, path, or redirects. Although the Skill is intended to capture `https://openrouter.ai/rankings`, the implementation permits Chromium to navigate to an arbitrary attacker-selected destination. This creates a server-side request forgery-like browser primitive. Depending on Chromium's supported protocols and environmental controls, the browser may be able to access: - Loopback services such as `127.0.0.1` or `[::1]` - Private network services - Link-local and cloud metadata endpoints - Attacker-controlled web pages - Services available only from the host's trusted network The risk is amplified because Chromium is launched with both `--no-sandbox` and `--disable-setuid-sandbox`. These options remove important browser isolation boundaries. If an attacker-controlled page exploits a Chromium vulnerability, the resulting code may execute with the privileges of the Node.js process rather than being contained by the Chromium sandbox. Redirects are also not restricted. Consequently, validating only the original URL would be insufficient unless every redirect destination is checked. ### Attack Path 1. An attacker gains influence over how `capture-rankings.mjs` is invoked, such as through an Agent-suppli ...[truncated 1744 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Remove support for a command-line URL if customization is unnecessary: ```js const url = DEFAULT_URL; ``` 2. If customization is required, enforce an exact allowlist: ```js function validateTarget(input) { const target = new URL(input); if (target.protocol !== 'https:') { throw new Error('Only HTTPS URLs are permitted'); } if (target.hostname !== 'openrouter.ai') { throw new Error('Only openrouter.ai is permitted'); } if (target.pathname !== '/rankings') { throw new Error('Only the rankings page is permitted'); } if (target.username || target.password || target.port) { throw new Error('Credentials and custom ports are not permitted'); } return target.href; } ``` 3. Resolve the destination hostname and reject loopback, private, link-local, multicast, and reserved IPv4 and IPv6 ranges. 4. Intercept browser requests and restrict navigation and subresource requests to explicitly approved origins. 5. Validate every redirect destination rather than checking only the initial URL. 6. Remove `--no-sandbox` and `--disable-setuid-sandbox`. Run Chromium as a dedicated non-root user with the browser sandbox enabled. 7. Apply outbound firewall or container network restrictions so the process cannot reach loopback administration services, private networks, or cloud metadata endpoints unless explicitly required. 8. Keep Chromium patched and use a browser version compatible with the installed `puppeteer-core` release. ]]>
