Back to skill

Security audit

md-to-html

Security checks for vulnerabilities and agentic risk

Overview

The skill matches its Markdown-to-HTML purpose, but generated HTML can run scripts hidden in untrusted Markdown when opened in a browser.

Review before installing. This skill should only be used on trusted Markdown unless the converter is fixed to escape or sanitize inline content and ideally add a restrictive Content Security Policy; do not open or share HTML generated from untrusted Markdown.

Vulnerability Patterns
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (1)

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. ]]>
Vulnerability Patterns
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (1)

Intent-Code Divergence

Medium
Confidence
98% confidence
Finding
The inline renderer inserts user-controlled text into HTML tags via regex replacements without first HTML-escaping the input. As a result, Markdown such as `<script>alert(1)</script>`, or HTML embedded around `**...**` / `` `...` ``, can be emitted into the final document and execute when the generated page is opened, creating a stored XSS issue in the produced HTML.

Static analysis

No suspicious patterns detected.