Back to skill

Security audit

URL to PDF

Security checks for vulnerabilities and agentic risk

Overview

This skill does what it says, but it accepts any URL and sends the rendered result back, which could expose local or private-network pages reachable from the agent host.

Install only if you trust users to supply safe public URLs, or run it in a sandbox with restricted outbound network access and no access to local/private services. Pin dependencies before production use and be aware that generated PDFs may overwrite files at the chosen output path.

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
url2pdf.py:93
Finding

Unrestricted Browser Navigation Enables Server-Side Request Forgery and Local Resource Disclosure

Content
View full analysis

Vulnerability Details

File Location: url2pdf.py, lines 93-106
Vulnerability Type: Unrestricted URL navigation / SSRF
Risk Level: High

The application accepts an arbitrary URL, directs a Chromium browser to navigate to it, converts the resulting page into a PDF, and exposes the PDF path for delivery to the requesting user.

python
def main():
    args = cli()
    url = args.url
    pdf_path = get_pdf_path(url, output=args.output)

    device = iPhone13Pro()

    with sync_playwright() as p:
        browser = p.chromium.launch()
        context = device.get_context(browser)
        page = context.new_page()
        try:
            page.goto(url, wait_until="domcontentloaded", timeout=30000)
            # Give it a small buffer for fonts/images to settle
            page.wait_for_timeout(3000)
        except Exception as e:
            print(f"Navigation warning: {e}")
            print("Proceeding to print...")

        device.save_as_pdf(page, pdf_path)
        print(pdf_path)
        browser.close()

The related instructions in SKILL.md, lines 35-45, direct the Agent to return the generated file:

markdown
### Agent Instructions

1. **Run the script**: Pass the url to be converted as an argument.
2. **Handle Output**: The script will output a path to a pdf file.
Use the `message` tool to send the pdf file to the user as a document message:
```json
{
   "action": "send",
   "filePath": "<filepath>"
}
text

### Technical Analysis

The value supplied through the positional `url` argument is passed directly to `page.goto()` without validating its scheme, destination hostname, resolved IP addresses, port, or redirect chain. Consequently, the browser can be directed toward resources accessible from the Agent host but not from the external user.

Potential targets include:

- Loopback services such as `http://127.0.0.1:PORT/`
- Private-network s
...[truncated 2312 chars]
Remediation
View remediation

Remediation Suggestions

  1. Permit only explicitly supported schemes, preferably https and, where necessary, http.
  2. Reject file:, data:, javascript:, ftp:, and all other non-approved schemes.
  3. Reject URLs containing embedded credentials.
  4. Resolve the destination hostname before navigation and reject every resolved address belonging to loopback, private, link-local, multicast, unspecified, reserved, or metadata-related ranges for both IPv4 and IPv6.
  5. Revalidate the destination after every redirect and terminate navigation before Chromium connects to a prohibited address.
  6. Defend against DNS rebinding by ensuring the address Chromium connects to is the validated address, or by enforcing destination restrictions at a network proxy or firewall.
  7. Run Chromium in an isolated container or sandbox with no access to host files and with tightly restricted outbound network access.
  8. Block access to cloud metadata addresses at the operating-system, container, or network layer.
  9. Apply an explicit hostname allowlist if the Skill only needs to support known public websites.
  10. Record rejected destinations without logging URL credentials or other sensitive query data.
  11. Add tests covering loopback, RFC 1918 ranges, IPv6, encoded addresses, redirect-to-private-host cases, DNS rebinding scenarios, and prohibited schemes.

T08 · Insecure Dependencies

Warning
Location
url2pdf.py:3
Finding

Unpinned Runtime Packages and Browser Artifacts Create a Supply-Chain Risk

Content
View full analysis

Vulnerability Details

File Location: url2pdf.py, lines 3-7
Vulnerability Type: Unpinned third-party dependencies
Risk Level: Medium

The script declares Playwright without a version constraint and permits any compatible python-slugify release at or above version 8.0.4:

python
# /// script
# requires-python = ">=3.12"
# dependencies = [
#     "playwright",
#     "python-slugify[unidecode]>=8.0.4",
# ]
# ///

The installation instructions in SKILL.md, lines 23-26, also retrieve a Chromium artifact without documenting a fixed, reviewed Playwright release:

markdown
playwright itself will be installed by uv automatically, while it also needs browser to be installed:
bash
uvx playwright install chromium

Technical Analysis

The absence of exact versions, artifact hashes, and a committed lockfile means separate installations can resolve to different package releases. The unconstrained playwright declaration can select a later release, while python-slugify[unidecode]>=8.0.4 permits later versions of the package and its applicable transitive dependencies.

The uvx playwright install chromium workflow is also dependent on the Playwright version selected at installation time and the corresponding remotely distributed browser artifact. Thus, the reviewed source tree does not fully determine the code and browser binaries that will execute.

This is a supply-chain weakness rather than evidence that the currently named packages are malicious. Exploitation requires compromise of a relevant upstream package, index, distribution channel, maintainer account, dependency artifact, or an unexpected incompatible upstream release.

Attack Path

  1. A relevant package release, transitive dependency, package-index response, or browser distribution artifact is maliciously modified or compromised.
  2. A user installs or runs the Skill after the compromised or unsafe releas ...[truncated 1238 chars]
Remediation
View remediation

Remediation Suggestions

  1. Pin every direct dependency to an exact reviewed version.
  2. Generate and commit a lockfile that captures all transitive dependency versions.
  3. Use integrity hashes for package artifacts where the package-management workflow supports them.
  4. Pin Playwright to an exact version and install the Chromium build corresponding to that reviewed release.
  5. Build the runtime and browser into a versioned, reviewed container image rather than downloading executable components during ordinary Skill invocation.
  6. Use a trusted package index or controlled internal mirror and disable unexpected fallback indexes.
  7. Verify browser downloads through vendor-provided integrity mechanisms and retain provenance information for built artifacts.
  8. Run automated dependency and container-image vulnerability scanning.
  9. Introduce an explicit review and testing process before updating package, lockfile, or browser versions.
  10. Execute dependency installation and the resulting browser process with least privilege and restricted filesystem and network access.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • 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 (4)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
83% confidence
Finding

The skill executes a local Python script and instructs the agent to pass user-controlled input to it, but the manifest declares no explicit tool scope or permissions boundaries. In an agent ecosystem, this weakens reviewability and containment because the skill can rely on ambient capabilities such as environment access without declaring them, increasing the chance of unexpected data exposure or unsafe execution behavior.

Content

No source excerpt is available for this finding.

Rp1

Medium
Category
MCP Rug Pull
Confidence
92% confidence
Finding

The installation step uses uvx playwright install chromium without pinning a specific version, so future runs may fetch different code or browser artifacts than were reviewed. This creates a supply-chain risk: a compromised or malicious upstream release could be installed and then used by the skill with local execution privileges.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
82% confidence
Finding

The script loads a user-supplied URL in a real browser context via page.goto, which necessarily makes outbound network requests and exposes client metadata such as the browser user agent and IP address to the destination. There is no explicit warning, confirmation, or disclosure in the code about this external network activity beyond the generic CLI description.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
70% confidence
Finding

device.save_as_pdf(page, pdf_path) creates or overwrites a PDF on the local filesystem, potentially under ~/Documents by default. While file output is part of the tool's purpose, the script does not explicitly warn or confirm the default write location or possible overwrite behavior.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.