Back to skill

Security audit

Dataify Scraper Builder

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly matches its scraper-building purpose, but it ships helper scripts with under-disclosed Dataify token, network, and local data-retention risks that users should review before installing.

Install only if you are comfortable sending target URLs, search terms, and fetched page content to Dataify using a DATAIFY_API_TOKEN. Avoid internal, private-network, confidential, or login-protected URLs; treat generated curl previews as untrusted commands; delete generated sample HTML when it may contain sensitive content; and rotate the token if using helpers that send it in URL query parameters.

Vulnerability Patterns
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (3)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/wait_for_task.py:36
Finding
API Token Exposed in HTTP GET Query Strings<![CDATA[ ## Vulnerability Details **File Location**: `scripts/wait_for_task.py:36-39`, with sensitive call sites at `scripts/wait_for_task.py:98-103` and `scripts/wait_for_task.py:130-134` **Vulnerability Type**: Sensitive credential exposure through URL query parameters **Risk Level**: High ### Vulnerable Code ```python def request_json(endpoint, params, api_key, timeout): url = endpoint + "?" + urllib.parse.urlencode(params) request = urllib.request.Request(url, method="GET") try: with urllib.request.urlopen(request, timeout=timeout) as response: ``` The API token is included in `params` at both call sites: ```python payload = request_json( STATUS_ENDPOINT, {"api_key": api_key, "task_id": task_id}, api_key, request_timeout, ) ``` ```python return request_json( DOWNLOAD_ENDPOINT, {"api_key": api_key, "task_id": task_id, "type": "json"}, api_key, request_timeout, ) ``` ### Technical Analysis `request_json()` URL-encodes the supplied parameters and appends them directly to the endpoint. Consequently, `DATAIFY_API_TOKEN` becomes part of the complete request URL for task-status and result-download requests. Although HTTPS encrypts the request in transit, it does not prevent the complete URL from being recorded by components such as: - Reverse-proxy and web-server access logs - API gateways - Network monitoring or application performance monitoring systems - Browser or HTTP debugging tools - Exception and diagnostic telemetry - Request tracing systems The response-body redaction performed later by the function does not protect the token already included in the request URL. Sending a credential in the URL also exceeds the minimum disclosure necessary because the other scripts already demonstrate that Dataify supports bearer authorization headers. ### Attack Path 1. A user configures a valid `DATAIFY_API_TOKEN`. 2. The user submits a task or runs `wait_for_task.py`. 3. The polling and download logic pl ...[truncated 761 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove `api_key` from all URL query parameters. 2. Send the credential exclusively through an authorization header: ```python def request_json(endpoint, params, api_key, timeout): url = endpoint + "?" + urllib.parse.urlencode(params) request = urllib.request.Request( url, headers={"Authorization": "Bearer " + api_key}, method="GET", ) ``` 3. Update the status and download calls so their parameter dictionaries contain only non-secret values such as `task_id` and `type`. 4. Configure the server to reject query-string API credentials. 5. Redact authorization headers and sensitive query keys in gateway, proxy, and telemetry configurations. 6. Review historical access logs for exposed tokens and rotate any credential that may have been recorded. 7. Add tests asserting that generated request URLs never contain the token or an `api_key` parameter. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/catalog_builder.py:66
Finding
Shell Command Injection Through Generated Curl Preview<![CDATA[ ## Vulnerability Details **File Location**: `scripts/catalog_builder.py:66-78` **Vulnerability Type**: Shell command injection through unsafe command generation **Risk Level**: High ### Vulnerable Code ```python def build_curl(tool, spider_parameters_json): return " \\\n".join([ "curl -X POST '{}'".format(BUILDER_URL), " -H 'Authorization: Bearer $DATAIFY_API_TOKEN'", " -H 'Content-Type: application/x-www-form-urlencoded'", " -d 'spider_name={}'".format(tool["spider_name"]), " -d 'spider_id={}'".format(tool["tool_sign"]), " -d 'spider_parameters={}'".format(spider_parameters_json), " -d 'spider_errors=true'", " -d 'file_name={{TasksID}}'", ]) ``` The value of `spider_parameters_json` originates from user-controlled `--params-json` content or a user-supplied values file. It is inserted into a single-quoted shell argument without escaping embedded single quotes. ### Technical Analysis Single quotes delimit literal strings in POSIX shells. If a supplied JSON string contains a single quote, it terminates the generated shell argument. Additional shell syntax can then be introduced before quoting is resumed. For example, a parameter value conceptually containing: ```text '; attacker_command; # ``` can break out of the generated `-d` argument when the preview is copied into a shell. The vulnerable function only prints the command when `--preview` is used; it does not execute the command directly. Nevertheless, the preview is explicitly formatted as a runnable curl command. Execution by a user or an automated agent would interpret the injected shell metacharacters. Fields sourced from the catalog, including `spider_name` and `tool_sign`, are also placed in single-quoted shell strings without a general-purpose shell-escaping routine. Those fields are less directly attacker-controlled in the reviewed project, but the same unsafe construction pattern applies. ### Attack Pa ...[truncated 1150 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Avoid producing executable shell commands from untrusted input. 2. Prefer printing a structured, non-executable request preview with separately encoded fields. 3. If a curl preview is required, apply a platform-appropriate quoting function to every dynamic argument. For POSIX shells, use `shlex.quote()`: ```python import shlex def build_curl(tool, spider_parameters_json): arguments = [ "curl", "-X", "POST", BUILDER_URL, "-H", "Authorization: Bearer $DATAIFY_API_TOKEN", "-H", "Content-Type: application/x-www-form-urlencoded", "-d", "spider_name=" + tool["spider_name"], "-d", "spider_id=" + tool["tool_sign"], "-d", "spider_parameters=" + spider_parameters_json, "-d", "spider_errors=true", "-d", "file_name={TasksID}", ] return " ".join(shlex.quote(argument) for argument in arguments) ``` 4. Clearly label previews with their intended shell and warn that previews containing untrusted values should not be executed. 5. When actual execution is needed, use an argument array through `subprocess.run(..., shell=False)` or continue using the existing `urllib` implementation rather than invoking a shell. 6. Add regression tests using single quotes, newlines, command substitutions, semicolons, and shell redirection characters. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/dataify_client.py:34
Finding
Public-URL Validation Allows Private and Local Network Destinations<![CDATA[ ## Vulnerability Details **File Location**: `scripts/dataify_client.py:34-41`, with forwarding sink at `scripts/dataify_client.py:91-97` **Vulnerability Type**: Incomplete SSRF and destination validation **Risk Level**: Medium ### Vulnerable Code ```python def normalize_url(value: str) -> str: value = value.strip() parts = urlsplit(value) if parts.scheme not in {"http", "https"} or not parts.netloc: raise ValueError("A public HTTP(S) URL is required") if parts.username or parts.password or not re.fullmatch(r"[A-Za-z0-9.-]+(?::\d+)?", parts.netloc): raise ValueError("URL credentials or invalid host names are not allowed") return value ``` The accepted URL is forwarded to the remote rendering service: ```python def unlock(url: str, token: str, geography: str = "us", clean_content: bool = True, timeout: float = 120) -> dict[str, Any]: payload = { "url": normalize_url(url), "type": "html", "js_render": "True", "clean_content": "true" if clean_content else "false", "country": geography.lower(), "follow_redirect": "True", "isjson": "1", } return _post(UNLOCKER_ENDPOINT, token, json.dumps(payload).encode("utf-8"), "application/json", timeout) ``` Equivalent gaps are present in the generated scraper template in `scripts/build_scraper.py:204-211`, where `args.url` is forwarded without calling `normalize_url()`, and in business-workflow URL actions forwarded by `scripts/business_workflow.py:160-168`. ### Technical Analysis The function claims to require a public HTTP(S) URL, but it validates only: - Scheme - Presence of a network location - Absence of embedded credentials - A restricted hostname character pattern It does not reject destinations such as: - Loopback addresses - Private network ranges - Link-local addresses - Unspecified, multicast, or reserved IP addresses - Hostnames that resolve to non-public addresses - Public hostnames that redirect or rebind to private address ...[truncated 1797 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Resolve the hostname and reject every address that is not globally routable. 2. Use Python’s `ipaddress` module to block loopback, private, link-local, multicast, reserved, and unspecified ranges for both IPv4 and IPv6. 3. Revalidate the destination after DNS resolution and at every redirect. 4. Apply the same validation to: - `scripts/dataify_client.py` - Generated scraper code in `scripts/build_scraper.py` - Source URLs accepted by `scripts/business_workflow.py` 5. Consider an explicit port allowlist, normally ports 80 and 443. 6. Canonicalize hosts before validation and account for alternate IP representations. 7. Require the remote Web Unlocker service to enforce equivalent server-side destination restrictions; client checks alone are not sufficient. 8. Add tests covering localhost, private IPv4 ranges, IPv6 loopback and unique-local ranges, link-local addresses, DNS rebinding, and public-to-private redirects. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • 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
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (5)

Lp3

Medium
Category
MCP Least Privilege
Confidence
97% confidence
Finding
The skill instructs the agent to use shell, network, environment inspection, and file output capabilities, but no permissions are declared. This creates a transparency and policy-enforcement gap: a reviewer or runtime may underestimate what the skill can do, increasing the chance of unintended command execution, network access, or local file manipulation under the guise of a documentation-only skill.

Tp4

High
Category
MCP Tool Poisoning
Confidence
89% confidence
Finding
The documented purpose is narrowly framed as inspecting a public site and designing a scraper, but the broader behavior includes task submission, status polling, downloading results, token/setup handling, and business-intelligence collection flows. This mismatch can mislead users and security controls about the actual operational scope, allowing more invasive data collection or external API activity than the description suggests.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The script writes the full fetched page HTML to sample.html in the output directory without any warning, minimization, or redaction. Because this skill targets arbitrary public websites, the saved content can include personal data, anti-bot challenge pages, query tokens, or copyrighted material, creating an avoidable local data-retention risk.

Missing User Warnings

Medium
Confidence
86% confidence
Finding
The generator emits a runnable scraper that automatically reads an API token from the environment and sends authenticated requests to an external endpoint, but the generator itself does not clearly disclose this behavior at generation time. That can lead users to run generated code without realizing it will transmit target URLs and credentials-backed requests to a third-party service.

Missing User Warnings

Medium
Confidence
80% confidence
Finding
The workflow sends user-supplied queries and URLs to external Dataify endpoints, which can expose sensitive business terms, target domains, or investigation subjects to third-party services. In a scraping/intelligence skill, this is contextual and expected behavior, but it still creates a real privacy and data-handling risk when users are not clearly informed at runtime or when sensitive inputs are allowed.

Static analysis

Detected: suspicious.exposed_secret_literal

File appears to expose a hardcoded API secret or token.

Critical
Code
suspicious.exposed_secret_literal
Location
scripts/task_runtime.py:38