Back to skill

Security audit

Markdown Canvas

Security checks for vulnerabilities and agentic risk

Overview

This Markdown-to-HTML skill is coherent, but review is warranted because generated pages can include executable HTML or JavaScript from the source Markdown and the skill tells users to open or present those pages.

Install only if you trust the Markdown files you will render, or inspect the generated HTML before opening or presenting it. Avoid using this skill on untrusted Markdown until the converter escapes or sanitizes raw HTML, validates link schemes, and prevents unsafe attributes. Review documents for secrets or private information before using the Canvas/share workflow.

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

Error
Location
scripts/convert.py:112
Finding
Stored HTML and JavaScript Injection in Generated Pages## Vulnerability Details **File Location**: `scripts/convert.py:49-54, 63-68, 72-79, 101-103, 112-128` **Vulnerability Type**: Unsanitized Markdown content and attribute injection **Risk Level**: High The converter places attacker-controlled Markdown content into an HTML document without first escaping or sanitizing it. Link destinations and code-fence language identifiers are also inserted into HTML attributes without contextual encoding. ### Vulnerable Code The ordinary paragraph path inserts the result of `process_inline_formatting()` directly into HTML: ```python # Normal paragraph text = process_inline_formatting(line) html.append(f'<p>{text}</p>') ``` Header and list content use the same unsafe formatting function: ```python # Headers if line.startswith('#'): level = len(line) - len(line.lstrip('#')) text = line.lstrip('#').strip() text = process_inline_formatting(text) html.append(f'<h{level}>{text}</h{level}>') i += 1 continue # Unordered lists if line.strip().startswith(('- ', '* ', '+ ')): if not in_list: in_list = True list_items = [] item_text = re.sub(r'^[\s\-\*\+]+', '', line).strip() item_text = process_inline_formatting(item_text) list_items.append(f'<li>{item_text}</li>') i += 1 continue ``` The inline formatter performs substitutions without HTML-escaping the original text. It also places an unrestricted link destination directly into an `href` attribute: ```python def process_inline_formatting(text): """Process inline markdown formatting""" # Bold text = re.sub(r'\*\*(.+?)\*\*', r'<strong>\1</strong>', text) text = re.sub(r'__(.+?)__', r'<strong>\1</strong>', text) # Italic text = re.sub(r'\*(.+?)\*', r'<em>\1</em>', text) text = re.sub(r'_(.+?)_', r'<em>\1</em>', text) ...[truncated 3374 chars]
Remediation
## Remediation Suggestions 1. Replace the regular-expression converter with a maintained Markdown parser configured to disable raw HTML, or sanitize the parser output with a strict HTML allowlist. 2. Escape all untrusted source text before constructing HTML. Apply encoding appropriate to the destination context, distinguishing element text from attribute values. 3. Preserve Markdown formatting through parsed tokens rather than escaping text after HTML tags have already been introduced. 4. Validate link schemes explicitly. Permit only required schemes such as `https`, `http`, and optionally `mailto`; reject `javascript:`, `data:`, `vbscript:`, and unknown schemes. 5. HTML-escape both link labels and attribute values. 6. Restrict code-fence language identifiers to a safe pattern such as `^[A-Za-z0-9_-]+$`. Reject or discard values that do not match. 7. Add `rel="noopener noreferrer"` to every link using `target="_blank"`. 8. Consider adding a restrictive Content Security Policy to the generated template, for example one that disallows inline scripts and external resources. This should be defense in depth rather than a replacement for sanitization. 9. Add regression tests for raw script elements, event-handler attributes, `javascript:` links, quotes in link destinations, malicious code-fence identifiers, and malformed Markdown. 10. Clearly document whether raw HTML is intentionally supported. If it is required, process it with an allowlist sanitizer before inserting it into the template.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (7)

Vague Triggers

Medium
Confidence
95% confidence
Finding
The usage section lists generic activation phrases such as "生成 HTML 页面", "分享这个文档", and "让 xxx.md 好看点" without clear constraints on when this skill should or should not be used. These phrases could match many ordinary requests beyond Markdown-to-HTML rendering, increasing the risk of unintended invocation.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The README explicitly suggests pushing rendered output to Canvas and opening generated HTML without warning that the markdown content may contain sensitive information. In an agent workflow, this can lead to unintended disclosure of private document contents to external surfaces or broader audiences if users treat rendering as a purely local formatting step.

Lp3

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding
The skill instructs the agent to read a user-supplied markdown file and write a generated HTML file, but it does not declare any explicit tool scope or permissions boundaries. That creates an authorization gap: an agent may perform filesystem operations implicitly without clear restrictions, increasing the chance of unintended reads or writes if the skill is invoked in the wrong context.

Vague Triggers

Medium
Confidence
88% confidence
Finding
The trigger phrases are broad enough to match common requests like 'share xxx.md as HTML' or 'make xxx.md pretty,' which can cause the skill to activate in situations where the user did not clearly consent to local file conversion and output generation. Over-broad routing increases the risk of unintended file handling or tool use, especially when paired with filesystem-writing behavior.

Natural-Language Policy Violations

Medium
Confidence
94% confidence
Finding
The HTML tag sets `lang="zh-CN"`, which forces a specific language/locale in the rendered output. Under the policy rules, a fixed locale is a finding when there is no nearby indication that users can choose their preferred language or that the locale restriction is intentionally justified.

Natural-Language Policy Violations

Low
Confidence
81% confidence
Finding
Natural-language guidance, examples, and usage instructions are presented exclusively in Chinese, which can impose a language requirement on users without opt-in. The file does not state that the skill is intended only for Chinese-speaking users or provide an alternative language option.

Missing User Warnings

Low
Confidence
84% confidence
Finding
The skill states that output HTML will be written alongside the source file or to a custom path, but it does not explicitly warn the user that invoking the skill modifies the filesystem. This can lead to surprising file creation or overwriting behavior, particularly if a custom output path is used or if the user expected a preview-only operation.

Static analysis

No suspicious patterns detected.