T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/download.py:83
- Finding
- JavaScript Injection in Generated Playwright Script Enables Arbitrary Command Execution<![CDATA[ ## Vulnerability Details **File Location**: `scripts/download.py`, lines 83–114 **Vulnerability Type**: JavaScript injection resulting in arbitrary local command execution **Risk Level**: High ### Vulnerable Code ```python script = f''' const {{ chromium }} = require('playwright'); (async () => {{ const browser = await chromium.launch({{ headless: true }}); const context = await browser.newContext(); const page = await context.newPage(); // Navigate to URL await page.goto('{url}', {{ waitUntil: 'networkidle' }}); // Wait for content await page.waitForTimeout(2000); // Handle authentication if needed // (Would need credentials for institutional access) // Get PDF link or download directly const pdfLink = await page.$('a[href$=".pdf"]'); if (pdfLink) {{ const href = await pdfLink.getAttribute('href'); console.log(href); }} await browser.close(); }})(); ''' # Save and run script with tempfile.NamedTemporaryFile(mode='w', suffix='.js', delete=False) as f: f.write(script) temp_path = f.name try: result = subprocess.run(['node', temp_path], capture_output=True, text=True) return result.stdout.strip() finally: os.unlink(temp_path) ``` ### Technical Analysis The `download_with_playwright` function inserts the caller-provided `url` directly into executable JavaScript source using a Python formatted string. The URL is placed between JavaScript single quotes without escaping or safe serialization: ```python await page.goto('{url}', ...) ``` An attacker can supply a value containing a single quote and additional JavaScript statements. This can terminate the intended string, alter the generated program, and invoke Node.js APIs such as `require('child_process')`. The generated source is written to a temporary `.js` file and executed with Node.js. Although `subprocess.run` uses an argument array and therefore does not itself invoke a she ...[truncated 1911 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Do not construct executable JavaScript by interpolating untrusted values into source code. 2. Prefer the Python Playwright API so the URL remains data rather than generated program text. 3. If a separate Node.js process is required, place the fixed JavaScript in a reviewed static file and pass the URL as a distinct command-line argument, standard-input value, or environment variable. 4. If serialization into JavaScript cannot be avoided, use a proper serializer such as `json.dumps(url)` rather than manually adding quotes. 5. Validate the parsed URL before navigation: - Permit only required schemes such as `https`. - Reject embedded credentials and malformed URLs. - Apply an explicit hostname allowlist if only supported academic platforms are intended. 6. Add a subprocess timeout and check the exit status: ```python subprocess.run( ["node", static_script_path, validated_url], capture_output=True, text=True, check=True, timeout=30, ) ``` 7. Run browser automation with the minimum filesystem, network, and process permissions necessary. 8. Add regression tests containing quotes, backslashes, line breaks, and JavaScript-like URL input to ensure values cannot change program syntax. ]]>
