T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/generate.py:681
- Finding
- Stored HTML and Script Injection Through Unescaped Report Data## Vulnerability Details **File Location**: `scripts/generate.py`, lines 681-694 **Vulnerability Type**: Stored HTML injection / cross-site scripting **Risk Level**: High ### Vulnerable Code ```python def _concepts_html(concepts): """Format concept labels for HTML display.""" if not concepts: return '' parts = [p.strip() for p in concepts.split('.') if p.strip()] if not parts: return '' html = f'<span style="color:#d29922;font-weight:600;">{parts[0]}</span>' for p in parts[1:]: html += f' <span style="color:#8b949e;font-size:11px;background:#21262d;padding:1px 6px;border-radius:3px;">{p}</span>' return html ``` The same unsafe interpolation pattern is used throughout report generation. For example, fields such as stock names, codes, reasons, tags, market summaries, source labels, and date labels are inserted directly into HTML fragments without contextual escaping. ### Technical Analysis Values loaded from `report_data.json` are treated as trusted HTML even though the documented workflow populates them from external market-data services. `_concepts_html()` places each concept string directly between HTML tags. It does not call `html.escape()`, sanitize markup, or validate the field against a restricted character set. An attacker who can influence a remote data response, the intermediate JSON file, or an upstream connector can supply markup such as: ```json { "concepts": "MarketTheme.<img src=x onerror=\"alert(document.domain)\">" } ``` This becomes active markup in the generated report: ```html <span style="color:#d29922;font-weight:600;">MarketTheme</span> <span style="..."><img src=x onerror="alert(document.domain)"></span> ``` Because the output is an HTML file intended to be delivered and opened by a user, the payload executes when the report is viewed in a browser. The static template does not establish a Content Security Policy th ...[truncated 1625 chars]
- Remediation
- ## Remediation Suggestions 1. Escape every untrusted value at the point where it is inserted into HTML: ```python from html import escape def html_text(value): return escape(str(value), quote=True) ``` Apply this helper to `concepts`, stock names, codes, reasons, labels, summaries, dates, source names, and every other string obtained from JSON or external services. 2. Correct `_concepts_html()` as follows: ```python from html import escape def _concepts_html(concepts): if not concepts: return '' parts = [p.strip() for p in str(concepts).split('.') if p.strip()] if not parts: return '' result = ( '<span style="color:#d29922;font-weight:600;">' f'{escape(parts[0])}</span>' ) for part in parts[1:]: result += ( ' <span style="color:#8b949e;font-size:11px;' 'background:#21262d;padding:1px 6px;border-radius:3px;">' f'{escape(part)}</span>' ) return result ``` 3. Prefer an auto-escaping template engine such as Jinja2 rather than assembling HTML with f-strings. Only explicitly mark constant, developer-authored fragments as safe. 4. Validate structured fields. For example, stock codes should match an expected character pattern, dates should be parsed as dates, and numeric values should reject non-numeric input. 5. Add a restrictive Content Security Policy, preferably via an HTTP header when hosted. For standalone reports, a suitable meta policy can provide partial defense in depth: ```html <meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; img-src data:;"> ``` 6. Add automated tests containing payloads such as `<script>`, `<img onerror>`, quotation marks, ampersands, and closing tags, and verify that the output contains encoded text rath ...[truncated 26 chars]
