T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/generate.py:267
- Finding
- Unescaped Report Data Enables Stored HTML and JavaScript Injection<![CDATA[ ## Vulnerability Details **File Location**: `scripts/generate.py`, lines 267–312 **Vulnerability Type**: Stored HTML injection / cross-site scripting **Risk Level**: High **Category**: T09: Insecure Skill Coding Practices ### Vulnerable Code ```python def generate_html(data, output_path): """Generates HTML file from data.""" # Helper to format list items def format_list(items): return "\n".join([f"<li>{item}</li>" for item in items]) # Prepare context for template context = { "title": data.get("title", "述职报告"), "goal": data.get("goal", ""), "q1_title": data.get("q1", {}).get("title", ""), "q1_subtitle": data.get("q1", {}).get("subtitle", ""), "q1_slogan": data.get("q1", {}).get("slogan", ""), "q1_items": format_list(data.get("q1", {}).get("items", [])), "q2_title": data.get("q2", {}).get("title", ""), "q2_subtitle": data.get("q2", {}).get("subtitle", ""), "q2_slogan": data.get("q2", {}).get("slogan", ""), "q2_items": format_list(data.get("q2", {}).get("items", [])), "q3_title": data.get("q3", {}).get("title", ""), "q3_subtitle": data.get("q3", {}).get("subtitle", ""), "q3_slogan": data.get("q3", {}).get("slogan", ""), "q3_items": format_list(data.get("q3", {}).get("items", [])), "q4_title": data.get("q4", {}).get("title", ""), "q4_subtitle": data.get("q4", {}).get("subtitle", ""), "q4_slogan": data.get("q4", {}).get("slogan", ""), "q4_items": format_list(data.get("q4", {}).get("items", [])), "summary_1": data.get("summary", ["", "", "", ""])[0], "summary_2": data.get("summary", ["", "", "", ""])[1], "summary_3": data.get("summary", ["", "", "", ""])[2], "summary_4": data.get("summary", ["", "", "", ""])[3], } html_content = HTML_TEMPLATE.format(**context) ``` ### Technical Analysis E ...[truncated 3203 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Apply contextual HTML escaping to every untrusted scalar and list item before interpolation: ```python from html import escape def escape_text(value): if not isinstance(value, str): raise TypeError("Report values must be strings") return escape(value, quote=True) def format_list(items): if not isinstance(items, list): raise TypeError("Report items must be a list") return "\n".join(f"<li>{escape_text(item)}</li>" for item in items) ``` 2. Escape all scalar fields, including the document title, goal, quadrant titles, subtitles, slogans, and summary values: ```python context = { "title": escape_text(data.get("title", "Default Report")), "goal": escape_text(data.get("goal", "")), # Apply escape_text to every remaining scalar field. } ``` 3. Prefer a template engine with automatic HTML escaping, such as Jinja2 configured with `select_autoescape`, rather than using unrestricted `str.format` interpolation. 4. Validate the complete JSON schema before rendering: - Require the top-level value to be an object. - Require `q1` through `q4` to be objects. - Require text fields to be strings. - Require each `items` value and `summary` to be arrays of strings. - Require exactly four summary entries or handle missing entries safely. - Enforce reasonable length limits to reduce rendering and resource-exhaustion risks. 5. Add a restrictive Content Security Policy to the generated document as defense in depth: ```html <meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; img-src data:; font-src 'none'; connect-src 'none'; script-src 'none'; frame-src 'none'; object-src 'none'; base-uri 'none'; form-action 'none'"> ``` 6. Render untrusted reports in a sandboxed, isolated browser context with network access disabled. Do not rely on browser isolation as a substitute for output encoding. 7. Add regression tests containing paylo ...[truncated 141 chars]
