T09 · Insecure Skill Coding Practices
- Location
- eval-viewer/generate_review.py:279
- Finding
- Stored Script Injection Through Embedded Evaluation Data<![CDATA[ ## Vulnerability Details **File Location**: `eval-viewer/generate_review.py:279-281` **Vulnerability Type**: Stored script injection in generated HTML **Risk Level**: High ### Vulnerable Code ```python data_json = json.dumps(embedded) return template.replace("/*__EMBEDDED_DATA__*/", f"const EMBEDDED_DATA = {data_json};") ``` The destination in `eval-viewer/viewer.html:647-649` is an executable script block: ```html <script> // ---- Embedded data (injected by generate_review.py) ---- /*__EMBEDDED_DATA__*/ ``` ### Technical Analysis `generate_html()` serializes evaluation data with `json.dumps()` and inserts the result directly into an executable HTML `<script>` element. The embedded object can contain evaluation prompts, generated text files, grading evidence, previous feedback, and other workspace content. JSON string escaping does not make arbitrary data safe for an HTML script context. In particular, `json.dumps()` does not neutralize the HTML parser sequence `</script>`. An evaluated output containing a payload such as: ```html </script><script>/* attacker-controlled JavaScript */</script> ``` would terminate the original script element before JavaScript parsing and introduce a new executable script element. Although most viewer rendering uses `textContent`, that protection occurs only after `EMBEDDED_DATA` has been parsed. It does not protect the initial server-generated script block. ### Attack Path 1. An untrusted or compromised Skill is included in an evaluation. 2. The Skill generates a text output containing a `</script>` sequence followed by attacker-controlled JavaScript. 3. `embed_file()` reads the generated output and includes it in the `embedded` data structure. 4. `generate_html()` serializes the structure and places it directly inside the viewer’s executable script block. 5. The user opens or refreshes the evaluation viewer. 6. The browser terminates the intended script block and executes the injected JavaScript in th ...[truncated 998 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Do not inject raw JSON into an executable script block. 2. Store serialized data in a non-executable element: ```html <script id="embedded-data" type="application/json"></script> ``` Then retrieve and parse its text: ```javascript const EMBEDDED_DATA = JSON.parse( document.getElementById("embedded-data").textContent ); ``` 3. Before embedding JSON into HTML, escape characters significant to the HTML parser. At minimum: ```python data_json = ( json.dumps(embedded) .replace("&", "\\u0026") .replace("<", "\\u003c") .replace(">", "\\u003e") .replace("\u2028", "\\u2028") .replace("\u2029", "\\u2029") ) ``` Escaping `<` prevents creation of a literal `</script>` sequence. 4. Add regression tests using output values containing: ```text </script> </script><script>alert(1)</script> <!-- U+2028 and U+2029 ``` 5. Add a restrictive Content Security Policy. Prefer external, locally packaged scripts and a nonce- or hash-based `script-src` policy rather than allowing arbitrary inline scripts. 6. Treat all prompts, generated artifacts, grading results, benchmark data, and previous feedback as untrusted input. ]]>
