T09 · Insecure Skill Coding Practices
- Location
scripts/generate_postmortem.py:416- Finding
Stored HTML and JavaScript Injection in Generated Reports
- Content
View full analysis
Vulnerability Details
File Location:
scripts/generate_postmortem.py, lines 416–490
Vulnerability Type: Unescaped user-controlled content in generated HTML
Risk Level: MediumVulnerable 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
View remediation
Remediation Suggestions
-
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) -
Do not use regex substitutions as a Markdown security mechanism. Use a maintained Markdown renderer configured to reject or escape raw HTML.
-
Sanitize rendered HTML with an allowlist sanitizer. Permit only required formatting tags and safe attributes. Remove at minimum:
script,iframe,object,embed, andstyleelements.- Inline event handlers such as
onclickandonerror. - Dangerous URL schemes such as
javascript:. - Unnecessary external-resource attributes.
-
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.
-
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)>.
-
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.
-
