Back to skill

Security audit

Render Url

Security checks for vulnerabilities and agentic risk

Overview

The skill appears to be a legitimate page-rendering helper, but it opens supplied URLs in Chromium and saves page contents locally.

Install only if you are comfortable letting the tool open the URLs you provide in Chromium and save rendered HTML or parsed page data as JSON files. Avoid internal, authenticated, or token-bearing URLs unless that is explicitly intended, clean up generated JSON files after use, and pin package versions if reproducible installs matter.

Vulnerability Patterns
  • 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
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (25)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The declared description is for a two-part skill whose primary value is rendering a live URL in headless Chromium and extracting post-JavaScript DOM content. The supplied code chunk is only the parsing/extraction stage (`parse_html.py`). It explicitly performs no network access, no Playwright/Chromium rendering, and no package installation. While the extraction fields align well with the description's extraction portion, the main declared capability—rendering a URL to obtain post-JavaScript content—is absent from this code. Therefore the description overstates what this specific code chunk actually does, making it a material mismatch rather than a mere implementation detail.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The core rendering portion matches the declared purpose at a high level: it uses Playwright/Chromium to load JS-rendered pages deterministically and does not use any LLM. However, the description specifically promises structured JSON extraction of page elements and content (title, headings, links, images, meta, text). This code does not perform that parsing; it only returns title plus the entire rendered HTML string and some navigation metadata. That is a material behavior difference, not a minor implementation detail. Additionally, the code persists the output JSON to local files with auto-incremented filenames, which is an undeclared side effect. Therefore the description does not accurately represent what this code chunk actually does.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The description centers on rendering live URLs in headless Chromium to capture JS-rendered content, then extracting structured data. The actual code chunk only contains unit tests for deterministic HTML parsing against local fixture files and inline HTML strings. The extraction behavior itself partially aligns with the structured fields claimed (title, headings, links, images, meta, text), but the primary declared capability—rendering a URL with Chromium after JavaScript execution—is not represented in this code. Because the supplied chunk is materially different in purpose and omits the core declared browser-rendering/network-installation behavior, this is a mismatch.

Tp4

High
Category
MCP Tool Poisoning
Confidence
93% confidence
Finding
The declared description centers on a two-part skill: rendering a URL in headless Chromium and extracting post-JavaScript page structure. The supplied code chunk instead contains integration tests for a parser CLI that reads step1 JSON files, parses HTML already present in those files, and checks subprocess/stdout/error/file behaviors. While the parsing aspect is somewhat adjacent to the declared extraction functionality, the critical rendering/fetching/browser-execution capability is absent from this code. The code’s primary purpose here is testing parse_html.py, not rendering URLs in Chromium. Therefore the description does not accurately represent what this specific code chunk actually does.

Description-Behavior Mismatch

Medium
Confidence
92% confidence
Finding
The manifest describes a `render-url` skill that renders a URL in headless Chromium and extracts structured JSON from the rendered page. The README instead documents a broader toolkit with a separate `parse-html` CLI and a combined `render-and-parse` command, expanding the apparent surface area beyond the single skill described in the manifest.

Lp3

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding
The skill declares capabilities that involve shell execution, network access, and local file writes, but does not specify any tool scope or permission boundaries. This increases the chance that an agent invokes the skill with broader privileges than intended, enabling unintended network requests or filesystem side effects.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The skill causes a real browser to visit user-supplied URLs, which sends network requests and browser-derived metadata to third-party sites, but the description does not warn users about this disclosure. Users may provide sensitive internal URLs or assume the tool operates locally, causing unintended exposure of browsing targets and related request data.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The skill writes rendered and parsed page contents to auto-incremented JSON files in the working directory, but this side effect is not surfaced as a user warning. Persisting page content locally can leak sensitive data from authenticated pages, internal sites, or pages containing tokens, and the files may remain after task completion.

Intent-Code Divergence

Medium
Confidence
94% confidence
Finding
The top-level comments describe a stdout-only deterministic extractor, but main() always persists the parsed result to disk via _write_output(). In this skill, the extracted content may include page text, links, images, and metadata from potentially sensitive authenticated or internal pages, so hidden persistence increases the risk of unintended data retention and downstream disclosure.

Description-Behavior Mismatch

Medium
Confidence
95% confidence
Finding
The module documentation states the extractor emits a single JSON object on stdout, but the implementation also writes results to auto-incremented files on disk. In an agent skill context, undocumented persistence can leak rendered page contents, metadata, or error details into the local workspace, which may expose sensitive data from pages the agent processes and violate caller expectations about side effects.

Description-Behavior Mismatch

Medium
Confidence
98% confidence
Finding
The manifest says this skill extracts structured JSON including headings, links, images, meta, and text from the rendered page. In code, the result schema contains only ok, url, final_url, status_code, title, html, and error, and the render path populates only title and raw HTML rather than the claimed structured page elements.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The tool automatically writes the full rendered page JSON, including raw HTML, to a local file for every run without requiring opt-in or warning the user. In this skill context, rendered pages may contain sensitive post-authentication or client-side material, so silent persistence increases the risk of local data exposure, accidental retention, and leakage through shared workspaces or logs.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def run(cmd):
    proc = subprocess.run(cmd, capture_output=True, text=True, timeout=60, cwd=ROOT)
    assert proc.stdout.strip(), f"empty stdout; stderr={proc.stderr}"
    lines = [l for l in proc.stdout.strip().splitlines() if l.strip()]
    assert len(lines) == 1, f"expected exactly one stdout line, got: {proc.stdout!r}"
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def run(cmd):
    proc = subprocess.run(cmd, capture_output=True, text=True, timeout=60, cwd=ROOT)
    assert proc.stdout.strip(), f"empty stdout; stderr={proc.stderr}"
    lines = [l for l in proc.stdout.strip().splitlines() if l.strip()]
    assert len(lines) == 1, f"expected exactly one stdout line, got: {proc.stdout!r}"
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def run_parser(args, cwd=None):
    cmd = [sys.executable, str(PARSER_SCRIPT)] + args
    proc = subprocess.run(cmd, capture_output=True, text=True, timeout=30, cwd=cwd or ROOT)
    return proc
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Unbounded Resource Access

Medium
Category
Excessive Agency
Content
os.remove(f)


def run_render(url, timeout=None, extra_args=None):
    cmd = [sys.executable, str(RENDER_SCRIPT), url]
    if timeout is not None:
        cmd += ["--timeout", str(timeout)]
Confidence
75% confidence
Finding
Skill allows unbounded resource consumption (API calls, storage, compute). Without rate limits or quotas, a compromised or misbehaving agent can cause denial-of-service or cost overruns.

Description-Behavior Mismatch

Low
Confidence
85% confidence
Finding
The manifest frames the skill as rendering a URL and extracting structured JSON from the page, which suggests a transformation-oriented tool. However, the README states that `render-url` automatically writes `rendered_page_N.json` files to disk on each run, adding durable side effects beyond simple extraction/output behavior.

Intent-Code Divergence

Low
Confidence
64% confidence
Finding
The documentation emphasizes constrained scope, but elsewhere in the README it presents a broader multi-command pipeline with `parse-html` and `render-and-parse`. While not a direct contradiction on crawling or batching, the 'intentionally does NOT do' section reinforces a narrow intent that is broader in practice than the manifest's single-command skill narrative.

Unverifiable Dependency: setuptools has 10 known advisory(ies) (CVE-2013-1633 (Setuptools vulnerable to Man-in-the-middle attacks); CVE-2025-47273 (setuptools has a path traversal vulnerability in PackageIndex.download that lead); CVE-2024-6345 (setuptools vulnerable to Command Injection via package URL) +7 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
40% confidence
Finding
Dependency has known vulnerabilities (CVEs). Using packages with unpatched security flaws exposes the environment to known exploits.

Intent-Code Divergence

Low
Confidence
84% confidence
Finding
The module documentation explicitly states 'It is a renderer, not a scraper.' That intent conflicts with the skill's public description, which claims deterministic extraction of structured content from the rendered page; the implemented code aligns with rendering-only behavior, not structured scraping/extraction.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Content
settings = dict(DEFAULTS)
    settings.update(load_config_file(args.config))
    for key in DEFAULTS:
        cli_value = getattr(args, key, None)
        if cli_value is not None:
            settings[key] = cli_value
    return settings
Confidence
50% confidence
Finding
Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Content
settings = dict(DEFAULTS)
    settings.update(load_config_file(args.config))
    for key in DEFAULTS:
        cli_value = getattr(args, key, None)
        if cli_value is not None:
            settings[key] = cli_value
    return settings
Confidence
50% confidence
Finding
Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Unpinned Dependencies

Low
Category
Supply Chain
Content
playwright>=1.40.0
beautifulsoup4>=4.12.0
Confidence
91% confidence
Finding
Using an unpinned dependency range for Playwright allows future installs to resolve to newer releases that may introduce breaking changes, supply-chain risk, or vulnerable versions indirectly pulled at install time. In a skill that renders arbitrary URLs in headless Chromium, dependency drift is more sensitive because browser automation libraries operate on untrusted web content and are security-relevant components.

Unpinned Dependencies

Low
Category
Supply Chain
Content
playwright>=1.40.0
beautifulsoup4>=4.12.0
Confidence
84% confidence
Finding
Using an unpinned dependency range for BeautifulSoup4 makes builds non-reproducible and can unexpectedly introduce vulnerable or incompatible releases in future installations. While this package is less inherently risky than a browser automation stack, it still expands supply-chain exposure and can affect parsing logic used on untrusted page content.

Missing User Warnings

Low
Confidence
84% confidence
Finding
This test file deletes JSON files via os.remove and launches real subprocesses via subprocess.run, but the code contains no confirmation prompt, user-facing log/print, or inline comment/docstring warning about these safety-relevant actions. For code files, SQP-2 applies when such operations lack any form of disclosure, and the file itself does not communicate these behaviors beyond test assertions.

Static analysis

No suspicious patterns detected.