Back to skill

Security audit

Scrapling Web Extractor

Security checks for vulnerabilities and agentic risk

Overview

The skill is a webpage-to-Markdown scraper, but it allows overbroad URL fetching beyond its public-web claim and its documentation does not match important runtime behavior.

Review this skill carefully before installing. Run it only in an isolated environment, provide only public URLs you trust, avoid sensitive networks where localhost or cloud metadata endpoints are reachable, and pin or review dependencies before following the install commands.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/scrape_to_markdown.py:163
Finding
Unrestricted URL Fetching Enables Server-Side Request Forgery<![CDATA[ ## Vulnerability Details **File Location**: `scripts/scrape_to_markdown.py`, lines 163–165; request sink at lines 184–209 **Vulnerability Type**: Server-Side Request Forgery through insufficient destination validation **Risk Level**: High ### Vulnerable Code ```python def validate_url(url): p = urlparse(url) return p.scheme in ("http", "https") and bool(p.netloc) ``` The validation result is subsequently trusted when fetching the URL: ```python for u in urls: if not validate_url(u): print(json.dumps({"ok": False, "error": f"Invalid URL: {u}"}, ensure_ascii=False)) sys.exit(1) out_dir = Path(args.output_dir) out_dir.mkdir(parents=True, exist_ok=True) results = [] for url in urls: item = { "url": url, "ok": False, "title": "", "status": None, "selector_used": None, "backend": None, "markdown": "", "preview": "", "output_markdown_file": None, "error": None, } try: page, backend = fetch_page( url=url, js=args.js, wait_selector=args.wait_selector or None, timeout=args.timeout, automatch_domain=args.automatch_domain or None, ) ``` ### Technical Analysis The URL validator only verifies that the scheme is HTTP or HTTPS and that a network location is present. It does not verify whether the destination is a public Internet host. Consequently, the validator accepts destinations such as: - Loopback addresses, including `127.0.0.1` and `::1` - Private IPv4 and IPv6 network ranges - Link-local addresses - Cloud instance metadata addresses - Reserved or unspecified addresses - Hostnames that resolve to internal addresses This behavior contradicts the public-web-only restriction documented in `SKILL.md`. It also exceeds the minimum network privileges required for public webpage scraping. The fetched response is converted to Markdown, written to the output direct ...[truncated 1900 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Resolve each destination hostname before making a request. 2. Reject every resolved IPv4 and IPv6 address classified as loopback, private, link-local, multicast, reserved, or unspecified. 3. Apply the same validation to every redirect target before following it. 4. Defend against DNS rebinding by ensuring that the connection uses an already validated address or by verifying the actual connected peer address. 5. Consider using an explicit domain allowlist when the expected scraping targets are known. 6. Reject URLs containing ambiguous host representations, malformed ports, or user-information components unless explicitly required. 7. Add tests covering at least: - `127.0.0.1` - `::1` - RFC1918 IPv4 ranges - IPv6 unique-local ranges - `169.254.169.254` - Hostnames resolving to internal addresses - Redirects from public hosts to internal addresses 8. Enforce outbound network restrictions at the container or firewall layer as defense in depth. ]]>

T08 · Insecure Dependencies

Warning
Location
README.md:27
Finding
Third-Party Packages and Browser Assets Are Installed Without Version or Integrity Pinning<![CDATA[ ## Vulnerability Details **File Location**: `README.md`, lines 27–31 **Vulnerability Type**: Unpinned third-party dependency installation **Risk Level**: Medium ### Vulnerable Code ```bash # Install Python dependencies manually (if needed) pip install scrapling html2text playwright install chromium python -m camoufox fetch ``` ### Technical Analysis The installation instructions retrieve Python packages and browser assets without specifying reviewed versions, lockfile constraints, or cryptographic hashes. Commands such as `pip install scrapling html2text` resolve mutable package versions and transitive dependencies at installation time. The Playwright and Camoufox commands also download external browser components whose versions and integrity are not constrained by the project documentation. This prevents reproducible installation and causes the effective trusted codebase to change over time without a corresponding review of the Skill. Installation can execute code supplied by packages or downloaded components with the privileges of the installing user. No evidence was found that these package names are currently malicious. The risk arises from mutable and insufficiently verified supply-chain inputs rather than from confirmed malicious package contents. ### Attack Path 1. A user follows the documented installation procedure. 2. The package manager or downloader resolves the latest available packages, transitive dependencies, and browser assets. 3. A compromised upstream release, package index, dependency, or distribution asset is selected. 4. The malicious component is downloaded into the Agent environment. 5. Package installation or later Skill execution runs attacker-controlled code with the privileges of the installing or executing account. ### Impact Assessment A compromised dependency could obtain the same privileges as the Skill process. Depending on installation and execution permissions, this may allow: - Reading files available to ...[truncated 430 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin all direct dependencies to explicitly reviewed versions. 2. Generate and commit a lockfile that also constrains transitive dependencies. 3. Use cryptographic hashes for Python packages, such as a requirements file installed with `pip --require-hashes`. 4. Pin Playwright and Camoufox versions and document the exact compatible browser artifact versions. 5. Verify downloaded browser assets using vendor-provided checksums or signatures where available. 6. Use a trusted, explicitly configured package index and disable unintended extra indexes to reduce dependency-confusion exposure. 7. Run dependency vulnerability and provenance checks in continuous integration. 8. Review dependency updates before changing locked versions. 9. Install and run the Skill as a dedicated, non-privileged account in an isolated environment. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (9)

Tp4

High
Category
MCP Tool Poisoning
Confidence
90% confidence
Finding
The documented behavior overstates or misstates what the skill does, including undeclared local file output and potentially missing or partial support for async, stealth, and dynamic modes. Security-relevant mismatches are dangerous because operators may trust controls, execution modes, or data-handling guarantees that are not actually implemented, leading to unsafe deployment decisions.

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill advertises and operationally requires network, shell execution, and local file read/write behaviors, but it declares no explicit tool scope or permissions. This creates a capability-transparency gap: a host agent may invoke a skill with broader powers than the user expects, increasing the risk of unintended file writes, arbitrary URL fetching, or shell-mediated misuse.

Missing User Warnings

Low
Confidence
85% confidence
Finding
This markdown file documents that `--output-dir` will save per-page `.md` files and a master `index.json`, but it does not explicitly warn that local files will be created or overwritten as part of execution. For markdown files, data-affecting behavior should be disclosed clearly so users understand the impact on their filesystem before running the skill.

Missing User Warnings

Low
Confidence
72% confidence
Finding
The markdown explains stealth options such as `--geoip`, `--proxy`, and WebRTC handling, but it does not clearly warn users that these features alter transmitted browser/network metadata and may route traffic through third-party infrastructure. For markdown files, privacy-relevant behavior should be disclosed explicitly when the skill can affect how user traffic and identifying data are handled.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Content
if obj is None:
        return ""
    for attr in ("html", "raw_html", "content", "markup", "body", "inner_html"):
        value = getattr(obj, attr, None)
        if callable(value):
            try:
                value = value()
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
for module_name, class_name in candidates:
        try:
            module = importlib.import_module(module_name)
            cls = getattr(module, class_name)
            for method_name in ("get", "fetch"):
                if callable(getattr(cls, method_name, None)):
                    return cls, method_name
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
module = importlib.import_module(module_name)
            cls = getattr(module, class_name)
            for method_name in ("get", "fetch"):
                if callable(getattr(cls, method_name, None)):
                    return cls, method_name
        except Exception as e:
            errors.append(f"{module_name}.{class_name}: {e}")
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
def fetch_page(url, js=False, wait_selector=None, timeout=30, automatch_domain=None):
    cls, method_name = load_fetcher(js=js)
    method = getattr(cls, method_name)

    kwargs = {}
    if js:
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
if automatch_domain and not js:
        try:
            instance = cls(automatch_domain=automatch_domain)
            method = getattr(instance, method_name)
        except Exception:
            pass
Confidence
50% confidence
Finding
Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Static analysis

No suspicious patterns detected.