T09 · Insecure Skill Coding Practices
- Location
exporter/html_exporter.py:94- Finding
Stored HTML and JavaScript Injection in Generated Skill Reports
- Content
View full analysis
Vulnerability Details
File Location:
exporter/html_exporter.py, lines 94–103 and 218–233
Vulnerability Type: Stored HTML/JavaScript injection
Risk Level: MediumVulnerable Code
python item = { 'name': skill['name'], 'status': skill.get('status', 'unknown'), 'version': skill.get('version', ''), 'source': _source_short(skill), 'emoji': skill.get('emoji') or '', 'homepage': skill.get('homepage') or '', 'description': skill.get('description', ''), 'big_category': cat[0], 'sub_category': cat[1], }javascript if (c.key === 'name') { const label = (skill.emoji ? skill.emoji + ' ' : '') + skill.name; const inner = skill.homepage ? '<a href="' + skill.homepage + '" target="_blank" style="color:#0066cc;text-decoration:none">' + label + '</a>' : label; return '<td><span class="skill-name">' + inner + '</span></td>'; } if (c.key === 'version') { return '<td>' + (skill.version ? '<span class="skill-version">v' + skill.version + '</span>' : '-') + '</td>'; } if (c.key === 'category') { return '<td><span class="category-tag">' + skill.big_category + '</span></td>'; } const v = skill[c.key]; return '<td>' + ((v === null || v === undefined || v === '') ? '-' : v) + '</td>';javascript data.forEach(skill => { const row = document.createElement('tr'); row.innerHTML = COLS.map(c => cellHtml(c, skill)).join(''); tbody.appendChild(row); });Technical Analysis
The HTML exporter includes skill metadata obtained from
openclaw skills list --json, including the skill name, description, emoji, homepage, version, and source. Installed third-party skills can therefore influence these fields.Although the metadata is serialized safely enough for placement inside the JavaScript source, the report subsequently concatenates the values into HTML strings. The resulting string is assigned to
row.innerHTMLwithout contextual HTML escaping.Consequently, attack ...[truncated 2185 chars]
- Remediation
View remediation
Remediation Suggestions
- Do not construct table cells using attacker-derived HTML strings. Create elements with DOM APIs and place metadata into
textContent:
javascript const td = document.createElement('td'); const span = document.createElement('span'); span.className = 'skill-name'; span.textContent = label; td.appendChild(span);- Create links through DOM properties rather than HTML concatenation. Parse and validate homepage URLs before assigning them:
javascript function safeHomepage(value) { try { const url = new URL(value); return url.protocol === 'https:' || url.protocol === 'http:' ? url.href : ''; } catch { return ''; } }-
Reject active URL schemes, including
javascript:,data:, andvbscript:. Prefer anhttps:-only policy unless another scheme is required by the product. -
Avoid
innerHTMLfor generated rows. Build each cell as a DOM node and append it to the row. If HTML rendering is genuinely required, apply a well-reviewed sanitizer with a restrictive element and attribute allowlist. -
Apply defense-in-depth browser restrictions through a Content Security Policy, for example by prohibiting inline event handlers and limiting outbound connections. This should supplement, not replace, contextual output encoding.
-
Add regression tests using metadata containing:
<img src=x onerror=...>- Attribute-breaking quotes
- Closing tags and nested elements
javascript:anddata:homepage URLs- Script-closing sequences
The tests should verify that payloads are rendered as inert text and that unsafe homepage schemes do not produce clickable links.
- Do not construct table cells using attacker-derived HTML strings. Create elements with DOM APIs and place metadata into
