T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/build_quiz_html.py:1051
- Finding
- HTML and JavaScript Injection Through Unescaped Custom Configuration<![CDATA[ ## Vulnerability Details **File Location**: `scripts/build_quiz_html.py`, lines 1051–1074 **Vulnerability Type**: Stored HTML/JavaScript injection in generated quiz files **Risk Level**: Medium ### Vulnerable Code ```python def render_html(houses_data, questions_data, papers_data, glossary_data, config_label="canon"): payload, wide, zero = build_quiz_payload(houses_data, questions_data, papers_data, glossary_data, config_label) title = houses_data["card"]["title"] page_title = "%s · %s" % (title, config_label) if config_label not in ("canon", "main") else title js = (JS_CORE .replace("__QUIZ_JSON__", json.dumps(payload, ensure_ascii=False, indent=None, separators=(",", ":"))) .replace("__WIDE_CHARS__", json.dumps(wide, ensure_ascii=False)) .replace("__ZERO_WIDTH_CHARS__", json.dumps(zero, ensure_ascii=False))) html = (HTML_TEMPLATE .replace("__LANG__", UI_LABELS["lang"]) .replace("__PAGE_TITLE__", page_title) .replace("__UI_START__", UI_LABELS["start"]) .replace("__UI_PREV__", UI_LABELS["prev"]) .replace("__UI_NEXT__", UI_LABELS["next"]) .replace("__UI_DICT__", UI_LABELS["dict_button_alt"] if config_label == "alt" else UI_LABELS["dict_button"]) .replace("__UI_ANNOT_ON__", UI_LABELS["annot_on"]) .replace("__UI_POP_CLOSE__", UI_LABELS["pop_close"]) .replace("__UI_DICT_CLOSE__", UI_LABELS["dict_close"]) .replace("__PICKED__", "·" + UI_LABELS["picked"]) .replace("__JS_CORE__", js)) return html ``` The generated JavaScript is placed inside this inline script context: ```html <script> __JS_CORE__ ... </script> ``` ### Technical Analysis The HTML generator supports custom configuration files through the documented `--config`, `--houses`, `--questions-file`, and `--glossary` options. Values loaded from those files are collect ...[truncated 3289 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. **Apply script-context-safe JSON serialization.** Escape characters that can affect HTML parsing after calling `json.dumps()`: ```python def safe_script_json(value): return ( json.dumps(value, ensure_ascii=False, separators=(",", ":")) .replace("&", "\\u0026") .replace("<", "\\u003c") .replace(">", "\\u003e") .replace("\u2028", "\\u2028") .replace("\u2029", "\\u2029") ) ``` Use this function for `payload`, `wide`, and `zero` before inserting them into an inline script. 2. **HTML-escape every value inserted into markup contexts.** In particular: ```python import html safe_page_title = html.escape(page_title, quote=True) html_output = HTML_TEMPLATE.replace("__PAGE_TITLE__", safe_page_title) ``` Apply equivalent escaping to every template substitution according to its destination context. 3. **Prefer a non-executable data container.** Place configuration data in a dedicated element: ```html <script id="quiz-data" type="application/json">...</script> ``` The JSON must still escape `<`, `>`, and `&`. Parse it with: ```javascript const QUIZ = JSON.parse(document.getElementById('quiz-data').textContent); ``` 4. **Treat custom configuration files as untrusted input.** Validate all string fields and reject control sequences or unexpected markup where HTML is not intended. 5. **Add regression tests.** Generate pages using values containing: - `</script>` - `<script>alert(1)</script>` - `<`, `>`, `&`, single quotes, and double quotes - U+2028 and U+2029 - Closing `</title>` and `</h1>` sequences Tests should assert that these values remain inert text and that no additional script or markup elements are created. 6. **Consider a restrictive Content Security Policy.** A CSP can provide defense in depth, although it should not replace contextual output encoding. For a self-contai ...[truncated 129 chars]
