T09 · Insecure Skill Coding Practices
Error
- Location
- templates/minimal.html:45
- Finding
- Unescaped briefing data enables DOM-based cross-site scripting in the minimal template<![CDATA[ ## Vulnerability Details **File Location**: `templates/minimal.html`, lines 45-48 **Vulnerability Type**: DOM-based cross-site scripting caused by unsafe HTML construction **Risk Level**: High ### Vulnerable Code ```html <script> const stats = {{stats}}; document.getElementById('stats').innerHTML = stats.map(s => `<div class="stat-card"><div class="stat-number">${s.num}</div><div class="stat-label">${s.label}</div></div>`).join(''); const points = {{points}}; document.getElementById('points').innerHTML = points.map((p, i) => `<li class="point-item"><span class="point-num">${i+1}</span><span class="point-text">${p.text}</span></li>`).join(''); </script> ``` ### Technical Analysis The Skill accepts content from arbitrary URLs, files, images, and text. Values derived from those inputs are embedded directly into JavaScript and subsequently interpolated into strings assigned to `innerHTML`. Fields such as `s.num`, `s.label`, and `p.text` are not HTML-escaped or sanitized. An attacker-controlled value containing active markup, such as an element with an event handler, will be parsed as HTML rather than displayed as text. The raw `{{stats}}` and `{{points}}` substitutions also enter a JavaScript context; if implemented as ordinary string replacement rather than safe serialization, crafted content could terminate the intended data structure and inject JavaScript directly. The template also places scalar placeholders such as `{{title}}`, `{{subtitle}}`, and `{{footer}}` into HTML contexts without evidence of contextual escaping. ### Attack Path 1. An attacker creates a webpage, document, image, or text input containing a crafted HTML or JavaScript payload. 2. A user asks the Skill to generate a briefing from that attacker-controlled content. 3. The extraction workflow places the payload into a statistic or briefing point. 4. The template engine substitutes the data into `{{stats}}` or `{{points}}`. 5. The page assigns the constructed str ...[truncated 858 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Replace `innerHTML` with DOM construction and assign untrusted values through `textContent`. 2. Serialize `stats` and `points` with a trusted JSON serializer rather than raw template replacement. 3. Escape `<`, `>`, `&`, U+2028, and U+2029 when embedding serialized data in an inline script. 4. Apply context-specific HTML escaping to `title`, `subtitle`, and `footer`. 5. If rich text is required, sanitize it with a strict allowlist that excludes scripts, event attributes, dangerous URLs, and active embedded content. 6. Add a restrictive Content Security Policy and disable unnecessary network and local-file access in the rendering browser. 7. Add regression tests using payloads such as `</script><script>alert(1)</script>` and `<img src=x onerror=alert(1)>`. ]]>
