T09 · Insecure Skill Coding Practices
- Location
- scripts/publish.py:367
- Finding
- Unescaped HTML Injection in Unsandboxed Browser with Unsafe Temporary Files<![CDATA[ ## Vulnerability Details **File Location**: `scripts/publish.py:367-388` **Vulnerability Type**: Unescaped HTML injection, weakened browser isolation, and unsafe predictable temporary files **Risk Level**: High ### Vulnerable Code ```python <div class="container"> <div class="title">{title}</div> <div class="diagram"> {''.join(f'<div class="box"><div class="box-title">{comp}</div></div>' for comp in components)} </div> </div> </body> </html> """ html_path = '/tmp/diagram.html' with open(html_path, 'w') as f: f.write(html_content) with sync_playwright() as p: browser = p.chromium.launch(headless=True, args=['--no-sandbox']) page = browser.new_page(viewport={'width': 1100, 'height': 400}) page.goto(f'file://{html_path}') page.wait_for_timeout(500) screenshot_path = '/tmp/diagram.png' page.screenshot(path=screenshot_path, full_page=True) browser.close() ``` ### Technical Analysis The diagram title and component values are interpolated directly into an HTML document without HTML escaping or validation. Because this document is subsequently loaded by Chromium, a caller capable of controlling `title` or `components` can inject active HTML, including scripts, event handlers, resource-loading elements, or malformed markup. Chromium is explicitly launched with `--no-sandbox`. This does not by itself grant injected JavaScript operating-system access, but it removes an important containment layer and increases the impact of any browser vulnerability or renderer compromise. The generated HTML and PNG also use globally predictable paths in the shared `/tmp` directory. Opening `/tmp/diagram.html` with the default Python write mode follows symbolic links. A local attacker may therefore pre-create that path as a symlink to another file writable by the publisher process. Similar race and content-integrity concerns affect the predictable screenshot path. ### Attack Path 1. An attacker supplies a diagr ...[truncated 1431 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Escape every untrusted value before inserting it into HTML: ```python from html import escape safe_title = escape(str(title), quote=True) safe_components = [escape(str(component), quote=True) for component in components] ``` 2. Prefer DOM text insertion, such as `textContent`, rather than constructing HTML through string concatenation. 3. Validate input length and type to prevent excessively large or malformed documents. 4. Remove `--no-sandbox` under normal execution. If a container requires special handling, configure the container and Chromium permissions so that the browser sandbox remains enabled. 5. Block all browser network requests during local rendering: ```python page.route("**/*", lambda route: ( route.continue_() if route.request.url.startswith("file://") else route.abort() )) ``` 6. Use a private temporary directory and unpredictable files: ```python import tempfile from pathlib import Path with tempfile.TemporaryDirectory(prefix="bear-diagram-") as directory: html_path = Path(directory) / "diagram.html" screenshot_path = Path(directory) / "diagram.png" ``` 7. Ensure temporary files are cleaned up after rendering. If the screenshot must survive the method, securely copy it to a caller-selected destination. 8. Do not run the Skill as root or another privileged account. ]]>
