Back to skill

Security audit

Scrapling Fetch

Security checks for vulnerabilities and agentic risk

Overview

This skill has a legitimate webpage-fetching purpose, but it ships unsafe code that can execute injected Python from a crafted URL and includes live-format billing behavior and credentials that need review before installation.

Review before installing. Do not run this skill on private, tokenized, internal, or sensitive URLs. The publisher should remove the hardcoded SkillPay key, rotate it, add explicit billing consent, make third-party proxying opt-in, block internal network targets, and replace python -c URL interpolation with safe argument passing before broad use.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/fetch.py:35
Finding
Arbitrary Python Code Execution Through Unsafe URL Interpolation<![CDATA[ ## Vulnerability Details **File Location**: `scripts/fetch.py:35-96` **Vulnerability Type**: Python source injection through untrusted input **Risk Level**: High ### Vulnerable Code ```python code = f""" from scrapling.fetchers import StealthyFetcher import json url = "{url}" page = StealthyFetcher.fetch(url, headless=True, network_idle=True) ``` The dynamically generated source is subsequently executed: ```python result = subprocess.run( [VENV_PYTHON, "-c", code], capture_output=True, text=True, timeout=120 ) ``` ### Technical Analysis The positional URL argument is embedded directly into a dynamically constructed Python program without escaping or serialization. The generated program is then passed to a Python interpreter using the `-c` option. Although `subprocess.run` is invoked without `shell=True`, that does not prevent this vulnerability. The attacker is injecting Python syntax into source code interpreted by the child Python process, rather than injecting shell metacharacters into a command line. A URL containing a quotation mark and additional Python syntax can terminate the intended string assignment and introduce attacker-controlled statements. There is no URL validation, quoting through `repr`, or safe argument-passing boundary. ### Attack Path 1. An attacker supplies a crafted value as the required `url` argument. 2. The value closes the generated `url = "..."` string. 3. The attacker adds valid Python statements to the generated source. 4. `fetch_with_scrapling` invokes the configured interpreter with `python -c`. 5. The injected statements execute with the privileges and environment of the Skill process. 6. The attacker can then invoke operating-system commands, access local files, or read credentials available to that process. This path is reached directly for URLs classified as WeChat or anti-bot sites and as a fallback when Jina fetching fails. ### Impact Assessment Successful exploitation provides arbi ...[truncated 538 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Do not generate Python source code from the supplied URL. - Import Scrapling and invoke `StealthyFetcher.fetch` directly in the current process. - If process isolation is required, place fixed implementation code in a separate reviewed script and pass the URL through a normal argument array or serialized standard input. - Validate the URL with `urllib.parse`, allow only explicitly supported schemes such as HTTPS, and reject control characters. - Run browser-based fetching in a restricted process with minimal filesystem access, no unnecessary secrets, and constrained network permissions. - Add regression tests using URLs containing quotes, backslashes, newlines, and Unicode control characters. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/fetch_paid.py:104
Finding
Arbitrary Python Code Execution in the Paid Fetching Script<![CDATA[ ## Vulnerability Details **File Location**: `scripts/fetch_paid.py:104-165` **Vulnerability Type**: Python source injection through untrusted input **Risk Level**: High ### Vulnerable Code ```python code = f""" from scrapling.fetchers import StealthyFetcher import json url = "{url}" page = StealthyFetcher.fetch(url, headless=True, network_idle=True) ``` The generated program is executed by a child interpreter: ```python result = subprocess.run( [VENV_PYTHON, "-c", code], capture_output=True, text=True, timeout=120 ) ``` ### Technical Analysis The paid implementation duplicates the unsafe source-generation behavior found in the free script. An attacker-controlled URL is inserted inside a quoted Python assignment without escaping and is then interpreted as Python source. The billing check does not mitigate this issue. The script also exposes a `--free` option, and execution can proceed without charging when no user ID is supplied. Consequently, an attacker does not necessarily need a valid paid account to reach the vulnerable fetching function. ### Attack Path 1. The attacker invokes `fetch_paid.py`, optionally using `--free`. 2. The attacker supplies a URL containing syntax that terminates the generated string. 3. The URL introduces additional Python statements into `code`. 4. The script starts the configured Python interpreter with the generated source. 5. The child interpreter executes the attacker-controlled statements locally. 6. The payload inherits the process account's accessible files, environment, and network connectivity. ### Impact Assessment The attacker can obtain arbitrary code execution under the Skill process account. Potential consequences include local data theft, credential disclosure, file modification, unauthorized outbound traffic, and compromise of the Skill workspace. Because this file also contains billing credentials, successful code execution may expose those credentials directly from the script ...[truncated 84 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Remove dynamic Python source construction entirely. - Pass the URL as a data value rather than executable source. - Use a fixed helper script with `subprocess.run([python, helper, url], shell=False)` if a separate process is required. - Validate supported schemes and reject malformed URLs and control characters. - Apply sandboxing to the browser process and remove billing secrets from its environment. - Consolidate the shared fetching implementation so the corrected logic is not duplicated across free and paid scripts. - Add security tests confirming that quotes and newline characters cannot modify interpreter behavior. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/fetch_paid.py:15
Finding
Hardcoded SkillPay API Credential in Distributed Source Code<![CDATA[ ## Vulnerability Details **File Location**: `scripts/fetch_paid.py:15-70` **Vulnerability Type**: Hardcoded secret and external credential transmission **Risk Level**: High ### Vulnerable Code The source contains a plaintext, live-format API credential. Its value is redacted below to avoid further disclosure: ```python BILLING_API_URL = 'https://skillpay.me' BILLING_API_KEY = 'sk_[REDACTED]' SKILL_ID = '7b495410-fb3e-44ff-9c71-9cd1260bb8b9' ``` The credential is sent to the external billing service: ```python resp = requests.get( f"{BILLING_API_URL}/api/v1/billing/balance", params={"user_id": user_id}, headers={"X-API-Key": BILLING_API_KEY} ) ``` ```python resp = requests.post( f"{BILLING_API_URL}/api/v1/billing/charge", headers={ "X-API-Key": BILLING_API_KEY, "Content-Type": "application/json" }, json={ "user_id": user_id, "skill_id": SKILL_ID, "amount": PRICE_PER_CALL } ) ``` ```python resp = requests.post( f"{BILLING_API_URL}/api/v1/billing/payment-link", headers={ "X-API-Key": BILLING_API_KEY, "Content-Type": "application/json" }, json={ "user_id": user_id, "amount": amount } ) ``` ### Technical Analysis A secret beginning with the documented `sk_` prefix is committed directly in a distributable Python file. Any user who downloads the Skill can recover and reuse it. Source-code permissions provide no meaningful protection because the interpreter must be able to read the value. The implementation also conflicts with the README instructions, which suggest configuring `BILLING_API_KEY` through an environment variable. The code never reads that environment variable and instead always uses the embedded credential. Sending an API key to its intended HTTPS service is necessary for authenticated billing, but embedding a shared credential in a public Skill is not necessary and violates least-secret-exposure principles. ## ...[truncated 967 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Revoke and rotate the exposed API key immediately. - Remove the credential from the repository and distributed package. - Load the key from a protected environment variable or secret manager. - Fail closed with a clear configuration error when the key is unavailable. - Use a separate, narrowly scoped key for each deployment instead of a shared package-wide secret. - Restrict the key server-side by permitted endpoints, Skill ID, rate limits, and spending limits. - Avoid returning sensitive billing details unless required. - Add secret scanning to development and release pipelines. - Review repository history and published package versions because deleting the current assignment does not remove the previously disclosed key. ]]>

T08 · Insecure Dependencies

Warning
Location
references/skill.json:6
Finding
Unpinned Dependencies and Mutable Installation Sources<![CDATA[ ## Vulnerability Details **File Location**: `references/skill.json:6-13` **Additional Locations**: `README.md:38-41`, `README.md:104-106`, `README.md:138-140`, `README.md:158-160` **Vulnerability Type**: Unpinned third-party dependency execution **Risk Level**: Medium ### Vulnerable Code ```json { "dependencies": [ "scrapling", "playwright", "curl_cffi", "patchright", "camoufox" ], "install_command": "pip install 'scrapling[all]' && playwright install chromium" } ``` The documentation also instructs users to run mutable installation commands: ```bash pip install 'scrapling[all]' playwright install chromium ``` ```bash npx clawhub install scrapling-fetch ``` ### Technical Analysis No dependency version, lockfile, package hash, or browser build checksum is specified. Installation therefore resolves whatever package versions are current at execution time. The `playwright install chromium` step downloads and installs an external browser artifact, while `npx` can retrieve and execute a mutable package version. There is no evidence that the named dependencies are currently malicious. The vulnerability is the absence of reproducible and integrity-verified dependency resolution, which creates an avoidable supply-chain execution path. ### Attack Path 1. A user follows the documented installation procedure. 2. Package managers resolve the latest available dependency and browser versions. 3. An upstream package, release account, registry artifact, or transitive dependency is compromised or changes incompatibly. 4. The package manager downloads the affected artifact. 5. Installation hooks or imported package code execute locally with the installing user's privileges. 6. The compromised component can access files, credentials, and network resources available to that user. ### Impact Assessment A compromised dependency or downloaded browser component could execute arbitrary code during installation or runtime. The scope is t ...[truncated 329 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Pin every direct dependency to a reviewed exact version. - Generate and distribute a lockfile that includes transitive dependencies. - Require cryptographic hashes for Python packages where supported. - Pin the Playwright package and corresponding browser build. - Pin the exact `npx` package version rather than resolving a mutable latest release. - Minimize optional extras and install only components required by the Skill. - Review package provenance and use official registries over unverified mirrors. - Add automated vulnerability and dependency-drift scanning to the release process. - Test upgrades in isolation before publishing updated pins. ]]>

other

Warning
Location
scripts/fetch.py:103
Finding
Disclosure of Complete Target URLs to an External Jina Proxy<![CDATA[ ## Vulnerability Details **File Location**: `scripts/fetch.py:103-117` **Additional Locations**: `scripts/fetch.py:145-161`, `scripts/fetch_paid.py:172-186`, `scripts/fetch_paid.py:232-248` **Vulnerability Type**: Sensitive URL disclosure to a third-party service **Risk Level**: Medium ### Vulnerable Code ```python def fetch_with_jina(url: str, max_chars: int = 50000) -> dict: import urllib.request import urllib.error jina_url = f"https://r.jina.ai/{url}" try: req = urllib.request.Request( jina_url, headers={'User-Agent': 'Mozilla/5.0'} ) with urllib.request.urlopen(req, timeout=30) as response: content = response.read().decode('utf-8') ``` Ordinary URLs are routed through this external service by default: ```python if args.fast: result = fetch_with_jina(args.url, args.max_chars) elif is_wechat_url(args.url) or is_antibot_site(args.url): result = fetch_with_scrapling(args.url, args.max_chars) else: result = fetch_with_jina(args.url, args.max_chars) if 'error' in result: result = fetch_with_scrapling(args.url, args.max_chars) ``` ### Technical Analysis The complete user-supplied target URL is appended to `https://r.jina.ai/` and transmitted to Jina. The same behavior exists in the paid implementation. URLs are not necessarily public or non-sensitive. They may contain signed query parameters, temporary access tokens, password-reset tokens, document identifiers, internal hostnames, usernames, or analytics identifiers. The code does not inspect or redact query strings, fragments, or embedded credentials before disclosure. Use of Jina is documented, so this is not covert exfiltration. However, proxying is the default path for ordinary websites rather than an explicit privacy-sensitive opt-in, and there is no local-only option that guarantees the URL will not be disclosed to a third party. ### Attack Path 1. A user supplies a URL containing a s ...[truncated 981 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Make third-party proxying explicitly opt-in instead of the default. - Add a documented local-only mode that never sends the target URL to Jina. - Warn users before transmitting complete URLs to an external service. - Reject URLs containing embedded usernames or passwords. - Detect and warn about sensitive query parameter names such as tokens, signatures, keys, and session identifiers. - Remove fragments and redact query parameters when they are not required to retrieve the resource. - Maintain an allowlist for destinations that may be proxied. - Block private, loopback, link-local, and internal-looking hosts from external proxy routing. - Document the external service's privacy implications and retention considerations. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • 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 Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (23)

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The README advertises a paid mode with 'automatic charging' and shows usage that triggers billing, but it does not clearly warn users about financial impact, consent flow, or when charges occur. In a skill context, this increases the risk of users invoking paid actions unintentionally or integrating the tool without adequate safeguards around spending.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The README includes direct billing API examples for balance checks and account charging, including API-key-based authentication, but omits prominent warnings about secret handling and irreversible billing actions. This is dangerous because users may copy/paste charging commands or expose credentials in shell history, logs, or shared environments, leading to unauthorized or accidental charges.

External Transmission

Medium
Category
Data Exfiltration
Content
#### 查询余额

```bash
curl -X GET "https://skillpay.me/api/v1/billing/balance?user_id=xxx" \
  -H "X-API-Key: YOUR_API_KEY"
```
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Rp1

Medium
Category
MCP Rug Pull
Confidence
70% confidence
Finding
npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.

Rp1

Medium
Category
MCP Rug Pull
Confidence
70% confidence
Finding
npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.

Vague Triggers

Medium
Confidence
94% confidence
Finding
The trigger list includes generic phrases like '抓取网页', '爬取内容', '无法访问', and '403错误' that can match many normal browsing or troubleshooting requests, causing the skill to activate outside its narrowly intended scope. In this skill, that is more dangerous because activation routes users into anti-bot bypass and third-party scraping workflows without a clear gating step or consent warning.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The skill documents Jina Reader and protected-site fetching but does not clearly warn that URLs and fetched content may be transmitted to third-party services or processed through anti-bot bypass tooling. This creates a data disclosure and compliance risk, especially if users provide private, sensitive, or access-controlled links assuming only local fetching will occur.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
The paid-mode setup instructs users to hardcode a live billing API key directly into source code, which encourages credential exposure through version control, logs, file sharing, backups, or accidental publication. Because the key is tied to billing operations, compromise could enable unauthorized charges, balance abuse, or takeover of the payment integration.

Natural-Language Policy Violations

Medium
Confidence
96% confidence
Finding
The description text is written only in Chinese ("智能网页内容抓取工具,支持绕过反爬机制") and provides no indication that users may choose another language or locale. This can violate language/locale policy when a skill's natural-language metadata is presented to a broader user base without opt-in or documented regional scope.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The skill invokes a stealth/anti-bot fetcher intended to bypass website protections, but this behavior is not surfaced to the user. Beyond legal and policy concerns, it causes opaque network access to arbitrary user-supplied destinations and increases exposure to SSRF-style misuse or collection from sites the user may not expect the tool to access in this manner. The explicit anti-bot bypass purpose makes the context more suspicious, not less.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
print(json.dumps(result, ensure_ascii=False))
"""
    
    result = subprocess.run(
        [VENV_PYTHON, "-c", code],
        capture_output=True,
        text=True,
Confidence
94% confidence
Finding
The subprocess itself is not inherently unsafe, but here it executes dynamically generated Python code built with direct string interpolation of a user-controlled URL. If the URL contains quotes or crafted payload text, it can break out of the string literal in the generated code and achieve arbitrary code execution in the spawned interpreter. The skill context increases risk because the feature is explicitly designed to fetch attacker-controlled remote content and accepts arbitrary URLs from users.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
This path sends user-supplied URLs to the third-party service `r.jina.ai` without any disclosure or consent mechanism. That can leak sensitive, internal, or private URLs to an external provider and may trigger unintended server-side requests on behalf of the user. In an agent skill context, this is more dangerous because users may assume local retrieval while the tool actually forwards targets to an outside service.

Natural-Language Policy Violations

Medium
Confidence
79% confidence
Finding
Natural-language strings in the file present the tool as Chinese-only and several user-facing outputs and descriptions are exclusively in Chinese, with no opt-in or locale selection mechanism. Under the policy, forcing a specific language without user choice is a language/locale policy violation unless the restriction is clearly justified.

Context-Inappropriate Capability

Medium
Confidence
91% confidence
Finding
The script embeds billing logic and a live billing API key directly in a fetch utility, creating unnecessary financial side effects and exposing a secret that can be reused by anyone with file access. In agent-skill context, hidden or weakly disclosed charging behavior is especially risky because a caller may invoke the tool expecting only content retrieval, not monetary operations.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
User identifiers and billing data are sent to an external billing service without any visible warning, consent flow, or privacy notice in this file. In an agent skill, silent transmission of identity and payment-related metadata to a third party is a meaningful privacy and trust risk, especially when coupled with charging behavior.

External Transmission

Medium
Category
Data Exfiltration
Content
def charge_user(user_id: str) -> dict:
    """扣费(余额不足自动返回充值链接)"""
    resp = requests.post(
        f"{BILLING_API_URL}/api/v1/billing/charge",
        headers={
            "X-API-Key": BILLING_API_KEY,
Confidence
96% confidence
Finding
This request transmits billing identifiers to an external payment endpoint and authorizes a charge using a hard-coded API key. If the key is exposed or abused, attackers could trigger unauthorized billing actions or interact with the payment backend outside intended controls.

External Transmission

Medium
Category
Data Exfiltration
Content
def get_payment_link(user_id: str, amount: float = 8.0) -> str:
    """生成充值链接"""
    resp = requests.post(
        f"{BILLING_API_URL}/api/v1/billing/payment-link",
        headers={
            "X-API-Key": BILLING_API_KEY,
Confidence
90% confidence
Finding
This call sends user billing information to an external service to generate payment links, again using the embedded API key. While expected for billing, it still represents external transmission of sensitive metadata and expands abuse potential if the secret or endpoint use is not tightly controlled.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The tool sends user-supplied target URLs and may retrieve page content through external services (`r.jina.ai` and potentially third-party scraping infrastructure) without explicit warning. This can leak sensitive URLs, internal links, or proprietary content to outside providers, which is especially dangerous for agent workflows where users may pass confidential targets.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
print(json.dumps(result, ensure_ascii=False))
"""
    
    result = subprocess.run(
        [VENV_PYTHON, "-c", code],
        capture_output=True,
        text=True,
Confidence
97% confidence
Finding
The subprocess invocation itself is not the core issue; the danger is that untrusted user input (`url`) is interpolated directly into a Python code string executed with `python -c`. A crafted URL containing quotes or Python syntax can break out of the string literal and achieve arbitrary code execution in the spawned interpreter.

Natural-Language Policy Violations

Low
Confidence
78% confidence
Finding
The README opens with Chinese title and content before later offering an English section, which can be interpreted as defaulting users into a specific language rather than clearly offering language choice upfront. The policy allows locale constraints when users are explicitly given a choice or justification is documented.

Natural-Language Policy Violations

Low
Confidence
89% confidence
Finding
The natural-language instructions and examples are presented entirely in Chinese, including the manifest description and operational guidance, with no indication that the skill supports other languages or that Chinese is a required locale. This can conflict with organizational language-choice policies when users are not given an opt-in or alternative.

Natural-Language Policy Violations

Low
Confidence
82% confidence
Finding
Natural-language strings in the module description and CLI help are presented only in Chinese, with no indication that other languages are supported. Under the policy, forcing a specific language without user opt-in can be a locale/language policy issue.

Intent-Code Divergence

Low
Confidence
81% confidence
Finding
The inline comment '添加计费信息' suggests the block only annotates the result with billing metadata. However, the code also invokes check_balance(), causing another external API request and retrieving fresh account data, which is more than merely attaching existing information.

Static analysis

No suspicious patterns detected.