T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/generate_postmortem.py:416
- Finding
- Stored HTML and JavaScript Injection in Generated Reports## Vulnerability Details **File Location**: `scripts/generate_postmortem.py`, lines 416–490 **Vulnerability Type**: Unescaped user-controlled content in generated HTML **Risk Level**: Medium ### Vulnerable Code ```python def generate_html(markdown_content, title): """Wrap markdown content in a simple HTML template.""" # Simple markdown-to-HTML conversion for key elements html = markdown_content # Headers html = re.sub(r'^# (.+)$', r'<h1>\1</h1>', html, flags=re.MULTILINE) html = re.sub(r'^## (.+)$', r'<h2>\1</h2>', html, flags=re.MULTILINE) html = re.sub(r'^### (.+)$', r'<h3>\1</h3>', html, flags=re.MULTILINE) # Bold html = re.sub(r'\*\*(.+?)\*\*', r'<strong>\1</strong>', html) # Italic html = re.sub(r'_(.+?)_', r'<em>\1</em>', html) # Code html = re.sub(r'`(.+?)`', r'<code>\1</code>', html) # Lists html = re.sub(r'^- (.+)$', r'<li>\1</li>', html, flags=re.MULTILINE) # Tables (simple conversion) def convert_table(match): lines = match.group(0).strip().split('\n') rows = [] for i, line in enumerate(lines): if '---' in line: continue cells = [c.strip() for c in line.strip('|').split('|')] tag = 'th' if i == 0 else 'td' row = ''.join(f'<{tag}>{c}</{tag}>' for c in cells) rows.append(f'<tr>{row}</tr>') return f'<table>{"".join(rows)}</table>' html = re.sub(r'(\|.+\|(?:\n\|.+\|)*)', convert_table, html) # Paragraphs (lines not already wrapped) lines = html.split('\n') processed = [] for line in lines: if line.strip() and not line.strip().startswith('<') and not line.strip().startswith('*'): processed.append(f'<p>{line}</p>') else: processed.append(line) html = '\n'.join(processed) return f"""<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scal ...[truncated 2865 chars]
- Remediation
- ## Remediation Suggestions 1. **Escape untrusted content before constructing HTML.** Use `html.escape()` for text inserted into HTML, including the document title: ```python from html import escape safe_title = escape(str(title), quote=True) ``` 2. **Do not use regex substitutions as a Markdown security mechanism.** Use a maintained Markdown renderer configured to reject or escape raw HTML. 3. **Sanitize rendered HTML with an allowlist sanitizer.** Permit only required formatting tags and safe attributes. Remove at minimum: - `script`, `iframe`, `object`, `embed`, and `style` elements. - Inline event handlers such as `onclick` and `onerror`. - Dangerous URL schemes such as `javascript:`. - Unnecessary external-resource attributes. 4. **Keep values separate from markup.** Escape incident fields, timeline event text, action-item values, and log messages at the point where they enter an HTML text or attribute context. 5. **Add regression tests** covering payloads in every attacker-controlled source, including: - Incident title and summary. - Timeline event text. - Action-item cells. - Parsed log messages. - Closing-tag payloads such as `</title>`. - Event-handler payloads such as `<img src=x onerror=alert(1)>`. 6. **Apply defense in depth** when reports are served over HTTP by using a restrictive Content Security Policy, for example one that blocks inline scripts and limits resource origins. CSP should supplement, not replace, escaping and sanitization.
