T09 · Insecure Skill Coding Practices
Error
- Location
- faq_publish.py:47
- Finding
- Stored Cross-Site Scripting in Generated HTML FAQ Pages<![CDATA[ ## Vulnerability Details **File Location**: `faq_publish.py`, lines 47–49, 347, and 359–392 **Vulnerability Type**: Stored Cross-Site Scripting (XSS) through unescaped HTML and attribute interpolation **Risk Level**: High ### Vulnerable Code The page title is inserted directly into HTML: ```python html = f"""<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>{title}</title> ``` Categories are inserted into both HTML text and attribute contexts without escaping: ```python for category in sorted(by_category.keys()): cat_id = category.lower().replace(" ", "-") html += f' <li><a href="#{cat_id}">{category}</a></li>\n' ``` FAQ fields and related-question data are also inserted directly into the generated document: ```python for category in sorted(by_category.keys()): cat_id = category.lower().replace(" ", "-") html += f' <div class="category" id="{cat_id}">\n' html += f' <h2 class="category-title">{category}</h2>\n' for entry in by_category[category]: active_class = "" if collapsible else "active" html += f' <div class="faq-item {active_class}" data-id="{entry.id}">\n' html += ' <div class="faq-question">\n' html += f' <span>{entry.question}</span>\n' if entry.priority in ["critical", "high"]: html += f' <span class="priority-badge priority-{entry.priority}">{entry.priority.upper()}</span>\n' if collapsible: html += ' <span class="icon">▼</span>\n' html += ' </div>\n' html += ' <div class="faq-answer">\n' html += f' <p>{entry.answer}</p>\n' if entry.tags or entry.related: ...[truncated 3608 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Apply context-appropriate output encoding to every dynamic value inserted into HTML: ```python from html import escape safe_title = escape(str(title), quote=True) safe_category = escape(str(category), quote=True) safe_question = escape(str(entry.question), quote=True) safe_answer = escape(str(entry.answer), quote=True) safe_tag = escape(str(tag), quote=True) ``` 2. Escape attribute values with `quote=True`. Do not reuse raw display text as an HTML ID. 3. Generate IDs through a strict allowlist: ```python import re def safe_html_id(value: str) -> str: value = re.sub(r'[^A-Za-z0-9_-]', '-', value) value = re.sub(r'-+', '-', value).strip('-') return value or "section" ``` 4. Validate database fields when loading and importing them. In particular: - Require strings for questions, answers, categories, priorities, products, IDs, and tags. - Restrict priorities to `low`, `normal`, `high`, or `critical`. - Restrict identifiers used in attributes to a safe character set. - Reject malformed nested structures. 5. Treat FAQ answers as plain text by default. If rich HTML is an intended feature, process it through a maintained allowlist-based HTML sanitizer and prohibit scripts, event-handler attributes, unsafe URLs, embedded frames, and active SVG content. 6. Add a restrictive Content Security Policy to generated pages as defense in depth. For example, move the existing inline JavaScript to a separate static file and use a policy similar to: ```html <meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'self'; script-src 'self'; img-src 'self' data:; base-uri 'none'; form-action 'none'"> ``` Output encoding remains mandatory because a CSP can be weakened or omitted during deployment. 7. Add regression tests for every output context, including: - `</p><script>alert(1)</script>` - `<img src=x onerror=alert(1)>` - `" onmouseover="alert(1)` - Categories containi ...[truncated 249 chars]
