T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/extract_session.py:154
- Finding
- Stored Script Injection in Generated Session Viewer<![CDATA[ ## Vulnerability Details **File Location**: `scripts/extract_session.py:154-155, 329, 341-342` **Vulnerability Type**: Stored script injection / unsafe HTML generation **Risk Level**: High ### Vulnerable Code ```python json_str = json.dumps(data, ensure_ascii=True) json_str = json_str.replace('</script>', '<\\/script>') ``` The serialized session data is then embedded directly into an executable script: ```python <script> var data = ''' + json_str + '''; ``` Some session metadata is also inserted into `innerHTML` without passing through the `esc()` function: ```javascript h += '<div><span class="badge badge-model">'+r.model+'</span> <span class="badge badge-tokens">'+(r.token_usage.input+r.token_usage.output)+' tok</span></div></div>'; ``` ```javascript h += '<div class="tool-item"><div class="tool-header"><span class="tool-name">'+tc.name+'</span><span style="color:#6e7681;font-size:0.75rem">'+tc.id+'</span></div>'; h += '<div class="tool-body tool-call">'+esc(JSON.stringify(tc.arguments,null,2))+'</div></div>'; ``` ### Technical Analysis The generated HTML contains session data inside an executable `<script>` element. The implementation attempts to prevent script termination by replacing the exact lowercase string `</script>`. HTML end-tag matching is ASCII case-insensitive, however. Consequently, variants such as `</ScRiPt>` are not modified by the Python replacement but are still interpreted by the browser as the end of the surrounding script element. An attacker-controlled value in a session log can therefore terminate the data script and introduce executable HTML or JavaScript. In addition, model names, tool names, and tool-call identifiers are concatenated directly into HTML strings assigned to `innerHTML`. A malicious value containing HTML with an event handler, such as an image element with an `onerror` attribute, could execute when the affected turn is rendered. Although ordinary message bodies and tool arguments are general ...[truncated 1767 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Do not embed untrusted JSON directly into an executable script element. 2. Store serialized data in a non-executable element, such as: ```html <script id="session-data" type="application/json"></script> ``` Populate that element using an HTML-safe serializer and parse its `textContent` with `JSON.parse()`. 3. Escape all characters that can affect HTML parsing, including at minimum `<`, `>`, and `&`. Do not rely on replacing only one lowercase closing tag. 4. Prefer constructing viewer elements with `document.createElement()` and assigning untrusted values through `textContent`. 5. If HTML-string construction remains necessary, call the escaping routine for every dynamic value, including: - `r.model` - `tc.name` - `tc.id` - `tr.tool_name` 6. Apply a restrictive Content Security Policy that disallows inline scripts and event handlers. Move viewer JavaScript to a separate static file if necessary. 7. Add regression tests containing mixed-case closing script tags, event-handler markup, quotes, angle brackets, and Unicode edge cases in every extracted field. ]]>
