Back to skill

Security audit

Markdown to PDF Advanced

Security checks for vulnerabilities and agentic risk

Overview

This Markdown-to-PDF skill is mostly purpose-aligned, but needs Review because it can auto-install unpinned packages at runtime and render document-controlled local or remote resources without tight controls.

Install only if you are comfortable with a document conversion skill that may change the Python environment when dependencies are missing. Prefer using it in an isolated virtual environment or container, preinstall pinned dependencies yourself, avoid running it as root, and do not process untrusted Markdown or CSS unless outbound network access and local file access are sandboxed.

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 (2)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/md_to_pdf.py:173
Finding
Unrestricted Local and Remote Resource Loading During PDF Rendering<![CDATA[ ## Vulnerability Details **File Location**: `scripts/md_to_pdf.py:173-227` and `scripts/md_to_pdf.py:289-295` **Vulnerability Type**: Server-Side Request Forgery and local resource disclosure through untrusted Markdown or CSS **Risk Level**: High ### Vulnerable Code ```python # Read markdown with open(input_path, 'r', encoding='utf-8') as f: md_content = f.read() # Convert markdown to HTML md = markdown.Markdown(extensions=[ 'tables', 'fenced_code', 'toc', 'nl2br' ]) html_body = md.convert(md_content) # Wrap in full HTML document html_content = f"""<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Document</title> </head> <body> {html_body} </body> </html>""" # Use custom CSS or default css_to_use = css_content if css_content else DEFAULT_CSS # Adjust page orientation if orientation == "landscape": css_to_use = css_to_use.replace("size: A4;", "size: A4 landscape;") # Convert to PDF font_config = FontConfiguration() html = HTML(string=html_content, base_url=str(Path(input_path).parent)) css = CSS(string=css_to_use, font_config=font_config) html.write_pdf(output_path, stylesheets=[css], font_config=font_config) ``` The custom CSS is also loaded without resource validation: ```python # Load custom CSS if provided css_content = None if args.css: if not os.path.exists(args.css): print(f"Warning: CSS file not found: {args.css}") else: with open(args.css, 'r', encoding='utf-8') as f: css_content = f.read() ``` ### Technical Analysis The converter passes Markdown-derived HTML and caller-selected CSS directly to WeasyPrint. No custom URL fetcher, URI allowlist, network restriction, or local-path containment check is configured. Python Markdown permits raw HTML in the source document under its normal behavior. Consequently, an untrusted document can introduce resource-loading elements such as images. Caller-controlled CSS can similarly contain resource directives such ...[truncated 2932 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. **Install a restrictive WeasyPrint URL fetcher** - Permit only explicitly approved URI schemes. - Resolve and validate hostnames before each request. - Block loopback, link-local, multicast, private, reserved, and cloud metadata address ranges. - Revalidate after redirects and DNS resolution to prevent redirect-based or DNS-rebinding bypasses. - Apply strict connection, read, response-size, and redirect limits. 2. **Restrict local file access** - Canonicalize every local path with `Path.resolve()`. - Require resources to remain under a dedicated document asset directory. - Reject absolute paths, unauthorized `file:` URLs, traversal outside the asset root, and symbolic-link escapes. 3. **Harden Markdown processing** - Disable or sanitize raw HTML when documents are not fully trusted. - Allow only a narrow set of safe tags and attributes. - Remove resource-bearing elements and attributes unless specifically required. 4. **Validate custom CSS** - Reject or sanitize `@import`, external `url(...)`, and remote `@font-face` declarations. - Prefer administrator-provided, pre-reviewed themes instead of arbitrary caller-supplied CSS. 5. **Isolate conversion** - Run rendering in a dedicated low-privilege container or sandbox. - Disable outbound network access by default. - Use a read-only filesystem and mount only the input and output directories required for the conversion. - Do not expose cloud credentials or metadata services to the conversion environment. 6. **Document the trust boundary** - Clearly state that untrusted Markdown, HTML, CSS, and remote resources require sandboxing. - Make remote resource loading opt-in rather than enabled implicitly. ]]>

T08 · Insecure Dependencies

Warning
Location
scripts/md_to_pdf.py:144
Finding
Automatic Installation of Unpinned Dependencies at Runtime<![CDATA[ ## Vulnerability Details **File Location**: `scripts/md_to_pdf.py:144-152` **Vulnerability Type**: Unsafe runtime dependency installation from mutable package sources **Risk Level**: Medium ### Vulnerable Code ```python def install_weasyprint(): """Try to install WeasyPrint automatically""" print("WeasyPrint not found. Attempting to install...") try: subprocess.check_call([ sys.executable, "-m", "pip", "install", "--quiet", "weasyprint", "markdown", "Pygments" ]) print("Successfully installed WeasyPrint!") return True except Exception as e: print(f"Failed to install WeasyPrint: {e}") return False ``` The installation is reached automatically if imports fail: ```python try: import markdown from weasyprint import HTML, CSS from weasyprint.text.fonts import FontConfiguration except ImportError: if not install_weasyprint(): raise RuntimeError("WeasyPrint is required but could not be installed") import markdown from weasyprint import HTML, CSS from weasyprint.text.fonts import FontConfiguration ``` ### Technical Analysis When required modules are unavailable, the converter automatically invokes pip and installs three packages without version constraints or integrity hashes: ```text weasyprint markdown Pygments ``` Pip may also resolve and install mutable transitive dependencies. The exact code installed therefore depends on package-index state and dependency resolution at execution time rather than on a reviewed, reproducible dependency set. Python package installation can execute package build and installation logic. A compromised upstream release, compromised configured package index, dependency-resolution attack, or malicious package served through an environment-specific pip configuration could therefore introduce code into the active Python environment. Using an argument array with `shell=False` prevents shell metacharac ...[truncated 1959 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. **Remove runtime installation** - Do not invoke pip from the document-conversion path. - Fail with a clear dependency error when required packages are unavailable. - Move dependency installation into an explicit deployment or setup phase. 2. **Pin and verify dependencies** - Use exact package versions for direct and transitive dependencies. - Maintain a reviewed lockfile. - Require package hashes, for example through a hash-locked requirements file and pip's `--require-hashes` option. 3. **Use an isolated environment** - Install dependencies into a dedicated virtual environment or immutable container image. - Avoid modifying system-wide or shared Python environments. - Build the image in a controlled pipeline and run conversion with a non-root account. 4. **Control package sources** - Use a trusted, authenticated package mirror or internal artifact repository. - Disable unexpected extra indexes and untrusted pip configuration. - Record and audit the source and digest of every installed artifact. 5. **Add supply-chain controls** - Scan pinned dependencies for known vulnerabilities. - Generate a software bill of materials. - Review dependency updates before deployment. - Rebuild only through a controlled CI/CD process. 6. **Align documentation with behavior** - Remove the claim that no network is required if automatic installation remains. - Preferably, remove automatic installation so local conversion is genuinely network-independent. ]]>
Vulnerability Patterns
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (14)

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
The manifest and description understate operational behavior by implying straightforward conversion while also enabling package installation and overstating backend behavior. Misleading capability declarations are dangerous because users and orchestrators may approve or invoke the skill without understanding that it can modify the environment or rely on different execution paths than advertised.

Context-Inappropriate Capability

High
Confidence
98% confidence
Finding
Calling pip from inside the skill alters the host environment and executes package installation logic during normal use, creating a strong supply-chain and persistence risk. In agent or CI environments, this can unexpectedly pull remote code, modify shared interpreters, and leave lasting changes far beyond generating a PDF.

Lp3

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding
The skill demonstrates shell execution and file access in its documented usage, but it does not declare an explicit tool scope or permissions boundary. This can lead to over-privileged execution in agent environments, making unintended command execution or file access harder to govern and review.

Vague Triggers

Medium
Confidence
95% confidence
Finding
The description says to use the skill for "any markdown-to-pdf conversion task," which is a broad natural-language trigger without clear boundaries or exclusion conditions. Although several specific trigger phrases are listed, this catch-all wording could overlap with many ordinary requests involving PDFs and markdown.

Description-Behavior Mismatch

Medium
Confidence
95% confidence
Finding
The skill claims no network is required, yet the documented support for remote images means conversion may fetch external resources. This creates a mismatch that can enable unexpected outbound requests, causing data leakage, privacy issues, or policy violations in restricted environments.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
```bash
# Install system dependencies first (Ubuntu/Debian)
sudo apt-get install python3-dev libffi-dev libxml2-dev libxslt1-dev

# Then install Python packages
pip3 install weasyprint markdown Pygments
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
```bash
# Install system dependencies first (Ubuntu/Debian)
sudo apt-get install python3-dev libffi-dev libxml2-dev libxslt1-dev

# Then install Python packages
pip3 install weasyprint markdown Pygments
Confidence
70% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Natural-Language Policy Violations

Medium
Confidence
98% confidence
Finding
The troubleshooting section abruptly provides guidance in Chinese, while the rest of the document is in English, and there is no user opt-in or explanation for the locale change. This can violate language/locale policy expectations by forcing a specific language for part of the user-facing instructions.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
dnf install -y google-noto-emoji-color-fonts

# Ubuntu/Debian
sudo apt-get install fonts-noto-color-emoji

# 2. 刷新字体缓存让系统识别新字体
fc-cache -fv
Confidence
70% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Description-Behavior Mismatch

Medium
Confidence
96% confidence
Finding
The skill advertises markdown-to-PDF conversion but also installs packages at runtime, which is an undisclosed capability expansion and environment modification. Hidden installation behavior erodes user trust and can be abused in automated agent contexts where users expect pure file conversion, not dependency changes or code retrieval from external repositories.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
Automatic installation occurs without an explicit prompt or safety warning, so a user asking to convert a document may unknowingly trigger network access and environment mutation. That lack of informed consent is especially risky for an agent skill, because it may run unattended with privileges the user did not intend to grant for package management.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
"""Try to install WeasyPrint automatically"""
    print("WeasyPrint not found. Attempting to install...")
    try:
        subprocess.check_call([
            sys.executable, "-m", "pip", "install", "--quiet",
            "weasyprint", "markdown", "Pygments"
        ])
Confidence
96% confidence
Finding
This subprocess call invokes pip to modify the runtime environment during a document-conversion task, which is a risky side effect unrelated to the core operation. If run in a privileged or trusted automation context, it can install unexpected code from package indexes, change dependency state, and create a supply-chain execution path without explicit user approval.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
cmd.extend(["-V", "geometry:landscape"])

    try:
        subprocess.run(cmd, check=True, capture_output=True, text=True)
        print(f"✓ PDF created: {output_path}")
        return True
    except subprocess.CalledProcessError as e:
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Missing User Warnings

Low
Confidence
79% confidence
Finding
Generating the PDF writes to the user-supplied output path, but the script does not disclose whether an existing file may be overwritten or otherwise affected. For file-writing operations, the rule asks for some visible disclosure, and this function only reports success after the write completes.

Static analysis

No suspicious patterns detected.