T09 · Insecure Skill Coding Practices
Error
- Location
- dashboard.html:550
- Finding
- <![CDATA[Stored DOM XSS through dynamically generated inline event handlers]]><![CDATA[ ## Vulnerability Details **File Location**: `dashboard.html:340-342`, `dashboard.html:550-554` **Vulnerability Type**: Stored DOM-based cross-site scripting **Risk Level**: High ### Vulnerable Code ```javascript function esc(s) { return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"').replace(/'/g,'''); } ``` ```javascript legend.innerHTML = series.map(s => '<div class="legend-item ' + (hiddenSeries.has(s.id) ? 'hidden-series' : '') + '" onclick="toggleSeries(\'' + esc(s.id) + '\')" role="button" tabindex="0" onkeydown="if(event.key===\'Enter\'||event.key===\' \')toggleSeries(\'' + esc(s.id) + '\')">' + (s.dashed ? '<div class="legend-dashed"></div>' : '<div class="legend-color" style="background:' + esc(s.color) + '"></div>') + '<span>' + esc(s.label) + '</span></div>' ).join(''); ``` ### Technical Analysis The `esc()` function performs HTML entity encoding, but the encoded value is inserted into a JavaScript string inside the `onclick` and `onkeydown` HTML attributes. HTML escaping alone does not make a value safe for a nested JavaScript execution context. When the browser parses the generated HTML, it decodes `'` back into a single quote before compiling the event-handler attribute as JavaScript. Consequently, an identifier containing JavaScript syntax can terminate the intended string and add arbitrary statements. The affected identifiers include model IDs obtained from parsed session logs and agent IDs derived from directory names under `~/.openclaw/agents`. The page's Content Security Policy permits `'unsafe-inline'`, so inline event handlers and injected inline JavaScript are allowed to execute. For example, an identifier shaped like the following can break out of the argument when its legend entry is activated: ```text ');alert(document.domain);// ``` After HTML entity decoding, the handler can become equivalent to: ```javascript toggleSeries('');alert(document.domain); ...[truncated 1639 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Do not generate inline event handlers with `innerHTML`. 2. Create legend elements using DOM APIs and attach handlers using `addEventListener()`: ```javascript legend.replaceChildren(); for (const s of series) { const item = document.createElement('div'); item.className = 'legend-item'; item.setAttribute('role', 'button'); item.tabIndex = 0; const activate = () => toggleSeries(s.id); item.addEventListener('click', activate); item.addEventListener('keydown', event => { if (event.key === 'Enter' || event.key === ' ') activate(); }); const label = document.createElement('span'); label.textContent = s.label; item.appendChild(label); legend.appendChild(item); } ``` 3. Use `textContent` for all identifiers and labels rather than interpolating them into markup. 4. Validate model and agent identifiers on the server against an explicit character and length policy where compatibility permits. 5. Remove the CSP allowance for `'unsafe-inline'`. Move scripts and styles into separate local files, or authorize fixed inline blocks with hashes or nonces. 6. Add regression tests using identifiers containing quotes, HTML entities, closing tags, and JavaScript fragments. ]]>
