T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/md2html.py:166
- Finding
- Generated HTML Allows Script Injection Through Unescaped Markdown Content<![CDATA[ ## Vulnerability Details **File Location**: `scripts/md2html.py`, lines 166–172; vulnerable output contexts also occur at lines 94, 104, 122, 138, 149, and 164 **Vulnerability Type**: Stored HTML and JavaScript injection **Risk Level**: Medium ### Vulnerable Code ```python def render_inline(text): """Render inline Markdown: bold, italic, code.""" text = re.sub(r'\*\*(.+?)\*\*', r'<strong>\1</strong>', text) text = re.sub(r'__(.+?)__', r'<strong>\1</strong>', text) text = re.sub(r'\*(.+?)\*', r'<em>\1</em>', text) text = re.sub(r'`([^`]+?)`', r'<code>\1</code>', text) return text ``` Representative vulnerable output sink at line 164: ```python html_parts.append(f'<p>{render_inline(para_text)}</p>') ``` The same unsafe renderer is inserted into table cells, list items, and blockquotes: ```python html_parts.append(f' <th>{render_inline(cell)}</th>') html_parts.append(f' <td>{render_inline(cell)}</td>') html_parts.append(f' <li>{render_inline(item)}</li>') html_parts.append(f' <p>{render_inline(" ".join(quote_lines))}</p>') ``` ### Technical Analysis `render_inline()` applies regular-expression substitutions that introduce formatting tags, but it never HTML-escapes the original untrusted Markdown text. Any raw HTML supplied in an affected Markdown context therefore remains active markup when incorporated into the generated document. This affects regular paragraphs, table headers and cells, ordered and unordered list items, and blockquotes. Heading and fenced-code-block handling separately uses `html.escape()`, but that protection does not cover the vulnerable inline-rendering paths. For example, the following Markdown paragraph is accepted without sanitization: ```markdown <script>alert(document.domain)</script> ``` It is emitted into the generated page as: ```html <p><script>alert(document.domain)</script></p> ``` Event-handler payloads and other active HTML elements can also be injected. The generated H ...[truncated 1880 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Escape all untrusted Markdown text before introducing any generated formatting tags. Do not run substitutions that preserve arbitrary raw HTML. 2. Prefer a maintained Markdown implementation with raw HTML disabled. If raw HTML must be supported, process the result with a robust allowlist-based HTML sanitizer. 3. If the custom parser is retained, tokenize Markdown formatting before escaping rather than applying formatting substitutions directly to raw input. Ensure that text inside paragraphs, table cells, list items, blockquotes, and inline-code spans is escaped with `html.escape()`. 4. Do not rely on regular expressions alone to sanitize HTML. Blocklists for tags or attributes are susceptible to parser differentials and malformed-markup bypasses. 5. Add a restrictive Content Security Policy to generated pages as defense in depth, for example by disallowing scripts when the output does not require JavaScript: ```html <meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; img-src data: https:; font-src 'none'; connect-src 'none'; script-src 'none'"> ``` 6. Add regression tests for every inline output context, including: - `<script>` elements. - Event-handler attributes such as `<img src=x onerror=...>`. - SVG-based active content. - Malformed tags and character entities. - Payloads inside tables, lists, blockquotes, and inline code. - Ordinary bold, italic, and code formatting to verify that secure escaping does not break expected rendering. ]]>
