Back to skill

Security audit

Markdown2pdf

Security checks for vulnerabilities and agentic risk

Overview

The skill converts Markdown as advertised, but it needs Review because it renders unsanitized HTML through wkhtmltopdf and accepts output paths that can escape the intended folder when used with untrusted input.

Install only if you are comfortable reviewing the converter and its dependencies. Use it for Markdown you trust, keep outputs in a dedicated folder, avoid exposing filename or Markdown content to untrusted users, and prefer a sandboxed environment for wkhtmltopdf until HTML sanitization and output-path validation are added.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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
Findings (3)

T09 · Insecure Skill Coding Practices

Error
Location
src/converter.py:387
Finding
Unsanitized HTML and Title Injection into the wkhtmltopdf Rendering Context<![CDATA[ ## Vulnerability Details **File Location**: `src/converter.py:387-423`, `src/converter.py:451-470`, and `src/converter.py:500-515` **Vulnerability Type**: Untrusted active HTML injection and unsafe document rendering **Risk Level**: High ### Vulnerable Code ```python def markdown_to_html(self, markdown_text: str, title: str = 'Document') -> str: """ Convert markdown to HTML. Args: markdown_text: Markdown text to convert. title: Document title. Returns: HTML string. """ # Replace emoji with PDF-compatible colored text labels markdown_text = replace_emoji_for_pdf(markdown_text, use_color=True) # Convert markdown to HTML with extensions html = markdown.markdown( markdown_text, extensions=[ 'extra', 'codehilite', 'toc', 'tables', 'fenced_code', 'nl2br' ] ) css = self.generate_css() # Add HTML structure with theme html_template = f"""<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>{title}</title> <style>{css}</style> </head> <body> {html} </body> </html> """ return html_template ``` The resulting HTML is then passed directly to the rendering engine: ```python pdfkit.from_string( html, str(output_path), options={ 'page-size': page_size, 'margin-top': margin, 'margin-right': margin, 'margin-bottom': margin, 'margin-left': margin, 'encoding': 'UTF-8', 'no-outline': None, 'print-media-type': None } ) ``` ```python imgkit.from_string( html, str(output_path), options={ 'width': width, 'format': 'png', 'quality': quality } ) ``` ### Technical Analysis The Markdown input is converted to HTML without an HTML sanitization stage. Python Markdown can preserve raw HTML ...[truncated 3033 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Sanitize generated HTML with a strict allowlist before invoking the renderer. Permit only document-formatting elements and safe attributes. 2. Escape the document title with `html.escape(title, quote=True)` before inserting it into the template. 3. Explicitly disable JavaScript in both PDF and image rendering options unless it is strictly required. 4. Block or remove dangerous URL schemes, including `file:`, `javascript:`, and unexpected `data:` resources. 5. Disable local-file access explicitly and allow it only for narrowly defined asset directories if needed. 6. Prevent external network access during conversion, or route requests through an allowlist-based proxy. 7. Run `wkhtmltopdf` in a sandbox or container with: - No credentials or environment secrets. - A read-only filesystem except for a dedicated output directory. - No access to cloud metadata endpoints or internal networks. - A non-privileged user and strict resource limits. 8. Add security tests covering raw `<script>`, `<iframe>`, remote images, loopback URLs, internal addresses, `file:` URLs, event-handler attributes, and title-element breakout attempts. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
src/converter.py:445
Finding
Unrestricted Output Filename Allows Path Traversal and Arbitrary File Overwrite<![CDATA[ ## Vulnerability Details **File Location**: `src/converter.py:445-451`, `src/converter.py:494-500`, and `src/converter.py:545-568` **Vulnerability Type**: Path traversal and unrestricted file write **Risk Level**: Medium ### Vulnerable Code PDF output paths are constructed directly from the supplied filename: ```python if output_filename is None: output_filename = 'output.pdf' output_path = (output_dir or self.output_dir) / output_filename ``` PNG output paths use the same pattern: ```python if output_filename is None: output_filename = 'output.png' output_path = (output_dir or self.output_dir) / output_filename ``` The multi-format conversion method also incorporates the supplied value without validation: ```python if output_filename is None: output_filename = 'output' output_dir = output_dir or self.output_dir output_dir.mkdir(parents=True, exist_ok=True) results = {} if 'pdf' in formats: pdf_path = self.convert_to_pdf( markdown_text, f'{output_filename}.pdf', output_dir, title, **kwargs ) results['pdf'] = pdf_path if 'png' in formats: png_path = self.convert_to_png( markdown_text, f'{output_filename}.png', output_dir, title, **kwargs ) results['png'] = png_path ``` ### Technical Analysis The code assumes that `output_filename` is a safe filename, but it can contain absolute paths or parent-directory components such as `../`. Python's `pathlib` does not automatically confine the resulting path beneath `output_dir`. In particular, joining an absolute path can cause the base directory to be discarded, while traversal components can resolve outside the intended output directory. No canonicalization, basename restriction, containment check, extension validation, or safe non-overwriting file-creation policy is enforced before the target path is supplied to the renderer. Although the CLI appends `.pdf` or `.png` in the mult ...[truncated 1721 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Restrict `output_filename` to a basename and reject values containing directory separators, absolute paths, `.` components, or `..` components. 2. Resolve the configured output directory and candidate target, then enforce containment: ```python base = Path(output_dir or self.output_dir).resolve() name = Path(output_filename) if name.is_absolute() or name.name != output_filename: raise ValueError("Output filename must be a basename") target = (base / name).resolve() if target.parent != base: raise ValueError("Output path escapes the configured directory") ``` 3. Validate that the final suffix matches the selected format. 4. Avoid overwriting existing files by default. Require an explicit overwrite option or create output files using an exclusive strategy. 5. Use a dedicated output directory with minimal filesystem permissions. 6. Apply the same validation in all convenience functions, direct API methods, and CLI entry points. 7. Add tests for absolute paths, nested paths, `../` traversal, symbolic-link escapes, pre-existing files, and platform-specific path separators. ]]>

T08 · Insecure Dependencies

Note
Location
install.sh:28
Finding
Unpinned and Inconsistently Installed Third-Party Dependencies<![CDATA[ ## Vulnerability Details **File Location**: `install.sh:28-30` and `requirements.txt:1-4` **Vulnerability Type**: Non-reproducible dependency installation and supply-chain exposure **Risk Level**: Low ### Vulnerable Code The installation script installs unconstrained packages directly from the configured Python package index: ```bash # 安装 Python 依赖 echo "📦 安装 Python 依赖..." $PIP_CMD install markdown pdfkit imgkit ``` The dependency file specifies only minimum versions and does not include integrity hashes: ```text markdown>=3.5.0 pdfkit>=1.0.0 imgkit>=1.2.3 wkhtmltopdf>=0.2.0 ``` Equivalent unconstrained installation commands also appear in `README.md`, `INSTALL.md`, `SKILL.md`, and `output/install_guide.md`. ### Technical Analysis The installer does not use `requirements.txt`; instead, it asks `pip` to install the latest versions satisfying no explicit version constraints. The requirements file itself uses open-ended lower bounds, allowing future versions to be selected without any change to the audited project. No package hashes are supplied to authenticate the exact artifacts expected by the project. As a result, installations are not reproducible. The code reviewed during this audit may execute with dependency versions materially different from those used during development or testing. This increases exposure to compromised upstream releases, unexpected behavioral changes, and newly introduced vulnerabilities. No evidence of an intentional dependency-confusion package, typosquatted package, or malicious package source was found. The risk arises from unsafe dependency-management practices rather than a confirmed malicious dependency. ### Attack Path 1. A user runs `install.sh` or follows the documented `pip3 install markdown pdfkit imgkit` command. 2. `pip` resolves package versions from its configured index at installation time. 3. A later release, compromised upstream artifact, or dependency-chain change is selected because exact ver ...[truncated 797 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Create a reviewed lock file containing exact versions for all direct and transitive Python dependencies. 2. Include cryptographic hashes and install with hash verification, for example through `pip install --require-hashes -r requirements.lock`. 3. Modify `install.sh` and all documentation to install from the lock file rather than naming packages without constraints. 4. Install dependencies inside a project-specific virtual environment instead of the user's global Python environment. 5. Separate the native `wkhtmltopdf` installation from Python dependencies and document a reviewed, supported native version. 6. Add automated dependency vulnerability scanning and a controlled update process. 7. Test each dependency update before refreshing pinned versions and hashes. ]]>
Vulnerability Patterns
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Rogue AgentSelf-Modification, Session Persistence
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (31)

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
brew install wkhtmltopdf

# Ubuntu/Debian
sudo apt-get install wkhtmltopdf

# CentOS/RHEL
sudo yum install wkhtmltopdf
Confidence
70% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
brew install wkhtmltopdf

# Ubuntu/Debian
sudo apt-get install wkhtmltopdf

# CentOS/RHEL
sudo yum install wkhtmltopdf
Confidence
70% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
brew install wkhtmltopdf

# Ubuntu/Debian
sudo apt-get install wkhtmltopdf

# CentOS/RHEL
sudo yum install wkhtmltopdf
Confidence
70% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
brew install wkhtmltopdf

# Ubuntu/Debian
sudo apt-get install wkhtmltopdf

# CentOS/RHEL
sudo yum install wkhtmltopdf
Confidence
70% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
brew install wkhtmltopdf

# Ubuntu/Debian
sudo apt-get install wkhtmltopdf

# CentOS/RHEL
sudo yum install wkhtmltopdf
Confidence
70% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
brew install wkhtmltopdf

# Ubuntu/Debian
sudo apt-get install wkhtmltopdf

# CentOS/RHEL
sudo yum install wkhtmltopdf
Confidence
70% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
sudo apt-get install wkhtmltopdf

# CentOS/RHEL
sudo yum install wkhtmltopdf
```

---
Confidence
70% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
sudo apt-get install wkhtmltopdf

# CentOS/RHEL
sudo yum install wkhtmltopdf
```

---
Confidence
70% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
sudo apt-get install wkhtmltopdf

# CentOS/RHEL
sudo yum install wkhtmltopdf
```

---
Confidence
70% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
sudo apt-get install wkhtmltopdf

# CentOS/RHEL
sudo yum install wkhtmltopdf
```

---
Confidence
70% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Session Persistence

Medium
Category
Rogue Agent
Content
## 🤝 Contributing

1. Fork the project
2. Create your feature branch (`git checkout -b feature/AmazingFeature`)
3. Commit your changes (`git commit -m 'Add some AmazingFeature'`)
4. Push to the branch (`git push origin feature/AmazingFeature`)
5. Open a Pull Request
Confidence
60% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Natural-Language Policy Violations

Medium
Confidence
91% confidence
Finding
The report content, headings, analysis, and disclaimer are all presented in Chinese, and there is no indication that the user opted into this language or that the skill is restricted to a Chinese-language or region-specific audience. Under the policy rule, forcing a specific language without opt-in is a natural-language policy violation.

Intent-Code Divergence

Medium
Confidence
96% confidence
Finding
The document states that it is 'for reference only' and not investment advice, yet elsewhere provides concrete portfolio allocations, buy/sell triggers, stop-loss levels, and timing guidance. This mismatch can mislead users into treating the content as de facto financial advice while reducing perceived accountability, creating legal, compliance, and user-harm risk.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
This markdown file instructs users to install Python packages, install wkhtmltopdf via system package managers, and execute `./install.sh`, all of which can change the user's environment or system state. The guide does not include any disclosure or warning about these effects, prerequisites, or the need to review the script before running it.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
The markdown gives explicit trading instructions such as entry ranges, add-on triggers, stop-loss levels, and profit-taking points before a later disclaimer says the report is not investment advice. In this context, the skill is more dangerous because it is framed as a professional-looking investment analysis report, which increases the likelihood that users will rely on the recommendations and suffer financial loss.

Intent-Code Divergence

Medium
Confidence
97% confidence
Finding
The document presents concrete trading actions elsewhere in the report, including suggested position sizing, buy ranges, add-on conditions, stop-losses, and price targets, while the disclaimer claims it is not investment advice. This contradiction can mislead readers into treating the content as actionable guidance without appropriately salient risk framing, creating compliance and consumer-harm risk.

Natural-Language Policy Violations

Medium
Confidence
88% confidence
Finding
SQP-3 applies to all file types and covers language or locale policy violations. This markdown file presents all user-facing content only in Chinese and does not indicate that the user opted into Chinese or that the skill is restricted to a Chinese-language/regional context.

Natural-Language Policy Violations

Medium
Confidence
97% confidence
Finding
This code replaces many emoji with Chinese text labels such as "[数据]", "[文档]", and "[全球]" in both colored and plain-text modes. Because the conversion behavior is built into the skill and no option is provided for language choice, it enforces a specific locale on all generated output, which violates the language/locale policy.

Natural-Language Policy Violations

Medium
Confidence
90% confidence
Finding
This code file contains natural-language descriptions and user-facing strings exclusively in Chinese, including the module docstring and function docstring. Under the stated policy, forcing a specific language without user opt-in is a language/locale policy violation.

Missing User Warnings

Low
Confidence
87% confidence
Finding
This markdown file documents behavior that affects user data and the filesystem by automatically converting content and saving outputs to an output directory. Under the markdown-specific SQP-2 criteria, the description should warn users that files will be created or overwritten so the behavior is clear before use.

Natural-Language Policy Violations

Low
Confidence
96% confidence
Finding
This file explicitly offers both English and Chinese sections, which provides user language choice rather than imposing a single required language or locale. Under the stated policy, this is not a violation and should not be flagged.

Missing User Warnings

Low
Confidence
83% confidence
Finding
The markdown describes document conversion features and usage examples, but it does not explicitly warn users that running the tool will write generated PDF/PNG files to the filesystem. Because this is a markdown skill description and file creation can affect user data or workspace contents, a brief disclosure would improve user awareness.

Natural-Language Policy Violations

Low
Confidence
96% confidence
Finding
This shell script presents all user-facing messages in Chinese, including installation status, errors, and usage instructions. The file does not offer a language option or explain that it is intentionally limited to a Chinese-speaking audience, which can violate language/locale policy requirements.

Natural-Language Policy Violations

Low
Confidence
84% confidence
Finding
All user-facing instructional text in the file is Chinese, and there is no indication that the user can choose another language or that the locale restriction is justified. This can violate a language/locale policy when a skill implicitly forces a specific language without user opt-in.

Unpinned Dependencies

Low
Category
Supply Chain
Content
markdown>=3.5.0
pdfkit>=1.0.0
imgkit>=1.2.3
wkhtmltopdf>=0.2.0
Confidence
96% confidence
Finding
The dependency is specified with a lower-bound version only, which allows newer unreviewed releases to be installed. This weakens build reproducibility and can unintentionally introduce vulnerable or breaking versions through normal dependency resolution.

Static analysis

No suspicious patterns detected.