T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/export.py:150
- Finding
- Stored HTML Injection in Exported Reports## Vulnerability Details **File Location**: `scripts/export.py`, lines 150–166 **Vulnerability Type**: Stored HTML injection caused by missing output encoding **Risk Level**: Medium ### Vulnerable Code ```python if goals.get('main_goal'): html += f" <h3>核心目标</h3>\n <p>{goals['main_goal']}</p>\n" if goals.get('sub_goals'): html += " <h3>子目标</h3>\n <ul>\n" for g in goals['sub_goals']: status = "✓" if g.get('completed') else "○" cls = "done" if g.get('completed') else "" html += f' <li class="{cls}">{status} {g["content"]}</li>\n' html += " </ul>\n" if goals.get('pending'): html += " <h3>待完成</h3>\n <ul>\n" for p in goals['pending']: html += f" <li>{p}</li>\n" html += " </ul>\n" ``` The affected fields are derived from conversation content without sanitization. For example, `scripts/extract_goals.py`, lines 37–53, returns user-controlled message text: ```python for msg in messages[:5]: if msg.get("role") != "user": continue content = msg.get("content", "") for keyword in GOAL_KEYWORDS: if keyword in content: sentences = re.split(r'[。.!?]', content) for sent in sentences: if keyword in sent and len(sent) > 5: return sent.strip(), "initial" if len(content) > 10: return content[:200], "initial" ``` ### Technical Analysis `Exporter.to_html()` constructs an HTML document by directly interpolating goal data into element bodies. The `main_goal`, `sub_goals[].content`, and `pending[]` values can originate from untrusted conversation messages processed by `extract_goals()`. No contextual HTML escaping is applied to these values. Consequently, HTML tags and event-handler attributes are interpreted as active markup rather than displayed as text. Although the summary field is partially escaped elsewhere in `to_html()`, that protection does not cover the affected goal fie ...[truncated 1869 chars]
- Remediation
- ## Remediation Suggestions Apply contextual HTML escaping to every dynamic value before inserting it into the report: ```python from html import escape if goals.get("main_goal"): main_goal = escape(str(goals["main_goal"])) html += f" <h3>Core Goal</h3>\n <p>{main_goal}</p>\n" if goals.get("sub_goals"): html += " <h3>Sub-goals</h3>\n <ul>\n" for goal in goals["sub_goals"]: status = "✓" if goal.get("completed") else "○" css_class = "done" if goal.get("completed") else "" safe_class = escape(css_class, quote=True) safe_status = escape(status) safe_content = escape(str(goal.get("content", ""))) html += ( f' <li class="{safe_class}">' f"{safe_status} {safe_content}</li>\n" ) html += " </ul>\n" if goals.get("pending"): html += " <h3>Pending</h3>\n <ul>\n" for item in goals["pending"]: html += f" <li>{escape(str(item))}</li>\n" html += " </ul>\n" ``` Additional hardening should include: 1. Escape all other dynamic HTML fields, including recommendations, labels, summaries, and future metadata. 2. Prefer a template engine with automatic escaping rather than manual string concatenation. 3. Do not rely on input sanitization alone; perform output encoding for the exact destination context. 4. Add a restrictive Content Security Policy to generated reports, such as: ```html <meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; img-src data:"> ``` 5. Add regression tests containing `<script>` elements, event-handler attributes, quotes, ampersands, malformed tags, and encoded payloads. 6. Verify generated reports display malicious test strings literally and do not create executable DOM elements.
