Back to skill

Security audit

Dataify Web Unlocker

Security checks for vulnerabilities and agentic risk

Overview

The advertised web-unlocker is mostly coherent, but the package also contains broader undisclosed search and business-intelligence scraping code plus credential-handling risks.

Review this before installing. Use it only if you are comfortable sending supplied URLs, fetched page content, and any optional headers or cookies to Dataify. Do not pass normal browser cookies, authorization headers, internal URLs, or long-lived secrets unless you intentionally want Dataify to receive them. Be aware the package contains broader search and business-scraping helpers that are not described by the main web-unlocker purpose.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/wait_for_task.py:34
Finding
Dataify API token exposed through URL query parameters<![CDATA[ ## Vulnerability Details **File Location**: `scripts/wait_for_task.py:34-36`, with vulnerable calls at `scripts/wait_for_task.py:104-110` and `scripts/wait_for_task.py:126-132` **Vulnerability Type**: Credential exposure through URL query strings **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") ``` The status request supplies the API token as an `api_key` query parameter: ```python payload = request_json( STATUS_ENDPOINT, {"api_key": api_key, "task_id": task_id}, api_key, request_timeout, ) ``` The result download request does the same: ```python return request_json( DOWNLOAD_ENDPOINT, {"api_key": api_key, "task_id": task_id, "type": "json"}, api_key, request_timeout, ) ``` ### Technical Analysis The API credential is encoded directly into the request URL instead of being sent in an authorization header. URLs are routinely recorded by HTTP servers, reverse proxies, gateways, observability platforms, network debugging tools, and exception-reporting systems. The following response-body redaction does not mitigate this exposure: ```python if api_key: text = text.replace(api_key, "<redacted>") ``` It only modifies the returned response text. It does not remove the token from the outgoing URL or infrastructure logs. This transmission is unnecessary because other project components already authenticate to Dataify using an `Authorization: Bearer ...` header. ### Attack Path 1. A user configures `DATAIFY_API_TOKEN` and submits or monitors a Dataify task. 2. `wait_for_task()` passes the token in the `api_key` query parameter. 3. `request_json()` constructs a URL containing the plaintext token. 4. A server, proxy, monitoring platform, or diagnostic component records the complete URL. 5. An attacker or unauthorized operator with access to those l ...[truncated 550 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove `api_key` from all query-parameter dictionaries. 2. Send the token through the standard 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 {}".format(api_key)}, method="GET", ) ``` 3. Update both status and download calls: ```python {"task_id": task_id} ``` ```python {"task_id": task_id, "type": "json"} ``` 4. If the remote API only accepts query-string authentication, request an API change or use a POST body over HTTPS. Until then, explicitly document the exposure and configure all relevant infrastructure to redact `api_key`. 5. Ensure error reporting, access logs, tracing systems, and debug output redact complete request URLs. 6. Rotate tokens that may already have appeared in logs. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/catalog_builder.py:87
Finding
Shell command injection through generated curl preview<![CDATA[ ## Vulnerability Details **File Location**: `scripts/catalog_builder.py:87-98` **Vulnerability Type**: Command injection through unsafe shell-command generation **Risk Level**: Medium ### 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}}'", ]) ``` ### Technical Analysis `spider_parameters_json` contains caller-controlled catalog parameter values. It is inserted into a single-quoted shell argument without shell escaping. A single quote inside a parameter can terminate the intended argument. Subsequent characters can then be interpreted by the shell as operators or commands. The affected function is used by preview mode: ```python if args.preview: print(build_curl(tool, payload_json)) return 0 ``` Preview mode does not execute the command itself, so exploitation requires a user, automation system, or Agent to copy and execute the generated command. Nevertheless, the output is presented as an executable curl command and therefore forms a practical command-injection boundary. ### Attack Path 1. An attacker supplies or induces the user to supply a catalog parameter containing a single quote and shell syntax. 2. The user runs `catalog_builder.py` with `--preview`. 3. `build_curl()` embeds the malicious value into the generated command without escaping it. 4. The user or Agent copies and executes the displayed curl command. 5. The shell parses the injected syntax as a separate local command. 6. The injected command executes with the privileges of the user running ...[truncated 571 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Prefer a non-executable JSON preview rather than generating shell source: ```python def build_preview(tool, rows): return { "method": "POST", "endpoint": BUILDER_URL, "authorization": "Bearer <redacted>", "form": { "spider_name": tool["spider_name"], "spider_id": tool["tool_sign"], "spider_parameters": rows, "spider_errors": "true", "file_name": "{{TasksID}}", }, } ``` 2. If a POSIX-shell curl command is required, apply `shlex.quote()` independently to every argument. 3. Generate separate, correctly escaped representations for POSIX shells, PowerShell, and Windows Command Prompt. 4. Clearly label generated commands as untrusted output requiring review. 5. Add tests containing apostrophes, command substitutions, newlines, semicolons, and shell metacharacters. 6. Do not automatically execute command text produced by preview mode. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/task_runtime.py:18
Finding
Unvalidated remote task identifiers embedded in resume commands<![CDATA[ ## Vulnerability Details **File Location**: `scripts/task_runtime.py:18-25`, with unsafe command construction at `scripts/task_runtime.py:41-48` and `scripts/task_runtime.py:50-60` **Vulnerability Type**: Command injection through unvalidated remotely supplied identifier **Risk Level**: Medium ### Vulnerable Code Dictionary-based API responses return task identifiers without character validation: ```python if isinstance(payload, dict): for key in ("task_id", "taskId"): value = payload.get(key) if value: return str(value) data = payload.get("data") if data is not payload: value = extract_task_id(data) if value: return value ``` The identifier is later inserted into a copyable shell command: ```python except TimeoutError as exc: waiter = os.path.abspath(os.path.join(os.path.dirname(__file__), "wait_for_task.py")) command = 'python3 "{}" --task-id "{}" --timeout {}'.format( waiter, task_id, int(float(wait_timeout)) ) raise RuntimeError("{}\nResume: {}".format(exc, command)) from None except KeyboardInterrupt: waiter = os.path.abspath(os.path.join(os.path.dirname(__file__), "wait_for_task.py")) command = 'python3 "{}" --task-id "{}" --timeout {}'.format( waiter, task_id, int(float(wait_timeout)) ) raise RuntimeError( "Monitoring interrupted. Do not resubmit the task.\nResume: {}".format(command) ) from None ``` A similar command generator exists in `scripts/wait_for_task.py:96-98`: ```python def resume_command(task_id, timeout): script_path = os.path.abspath(__file__) return 'python3 "{}" --task-id "{}" --timeout {}'.format(script_path, task_id, int(timeout)) ``` ### Technical Analysis Plain-string API responses are subject to a restrictive validation rule: ```python if re.fullmatch(r"[A-Za-z0-9_-]{8,128}", candidate): return candidate ``` However, identifiers extracted from dictionary fields bypass that ru ...[truncated 1554 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Apply the same validation to identifiers from every response shape: ```python TASK_ID_PATTERN = re.compile(r"[A-Za-z0-9_-]{8,128}") def validated_task_id(value): candidate = str(value or "").strip() return candidate if TASK_ID_PATTERN.fullmatch(candidate) else None ``` 2. Reject the API response if a task identifier contains whitespace, quotes, control characters, separators, or shell metacharacters. 3. Avoid emitting executable command strings. Prefer structured instructions: ```json { "program": "python3", "arguments": [ "/absolute/path/wait_for_task.py", "--task-id", "validated-task-id", "--timeout", "600" ] } ``` 4. If a shell command must be displayed, use `shlex.quote()` for POSIX shells and platform-specific quoting for PowerShell or Command Prompt. 5. Add tests for malicious identifiers containing `"`, `'`, `$()`, backticks, semicolons, newlines, and option-like prefixes. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/invoke-dataify-web-unlocker.py:21
Finding
Caller-supplied authentication headers and cookies forwarded to an external service without safeguards<![CDATA[ ## Vulnerability Details **File Location**: `scripts/invoke-dataify-web-unlocker.py:21-27` and `scripts/invoke-dataify-web-unlocker.py:34-47` **Vulnerability Type**: Sensitive credential disclosure to a third-party service **Risk Level**: Medium ### Vulnerable Code The command-line interface accepts arbitrary headers and cookies: ```python parser.add_argument("--block-resources", default="", help="Resource blocking setting.") parser.add_argument("--clean-content", default="", help="Clean content setting.") parser.add_argument("--country", default="us", help="Country code.") parser.add_argument("--headers", default="", help="Request headers as a string.") parser.add_argument("--cookies", default="", help="Cookies as a string.") parser.add_argument("--wait", default="", help="Wait time before capture.") parser.add_argument("--wait-for", default="", help="Selector or condition to wait for.") ``` Those values are copied directly into the payload sent to Dataify: ```python def build_payload(args: argparse.Namespace) -> dict: return { "url": args.url, "type": args.type, "js_render": args.js_render, "block_resources": args.block_resources, "clean_content": args.clean_content, "country": args.country, "headers": args.headers, "cookies": args.cookies, "wait": args.wait, "wait_for": args.wait_for, "follow_redirect": args.follow_redirect, "isjson": args.isjson, } ``` The complete payload is transmitted to the external endpoint: ```python body = json.dumps(payload).encode("utf-8") request = urllib.request.Request( ENDPOINT, data=body, headers={ "Authorization": f"Bearer {token}", "Content-Type": "application/json", }, method="POST", ) ``` ### Technical Analysis Passing cookies or custom headers can be functionally relevant for fetching authenticated pages. However, these fields may contain reusable session cookies, b ...[truncated 2004 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Display a clear warning and require explicit informed confirmation before forwarding non-empty headers or cookies. 2. Reject or separately gate high-risk headers such as: - `Authorization` - `Proxy-Authorization` - Long-lived API-key headers 3. Redact cookie and header values in dry-run output: ```python "headers": "<redacted>" if args.headers else "", "cookies": "<redacted>" if args.cookies else "", ``` 4. Reject carriage returns, line feeds, null bytes, and malformed header representations. 5. Encourage temporary, narrowly scoped, read-only credentials instead of normal browser sessions or long-lived tokens. 6. Avoid passing secrets directly on command lines because they may be retained in shell history or exposed through process inspection. Support protected standard input or a permission-restricted credential file where appropriate. 7. Document that these values are transmitted to Dataify and identify the relevant retention and privacy considerations. 8. Never persist forwarded cookies or headers in reports, debug logs, state files, or error messages. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • 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 (8)

Lp3

Medium
Category
MCP Least Privilege
Confidence
87% confidence
Finding
The skill clearly instructs use of environment variables, shell commands, local shell scripts, and outbound network requests, yet no permissions are declared. That mismatch weakens platform trust boundaries because users and policy engines are not transparently informed that the skill can access sensitive execution capabilities.

Context-Inappropriate Capability

High
Confidence
98% confidence
Finding
The skill is presented as a narrow web-unlocker for fetching HTML or screenshots, but the code orchestrates broader search, shopping, news, review, and platform-specific scraping workflows. This scope expansion can cause users to unknowingly submit subjects, queries, and URLs to multiple external collection capabilities beyond the declared purpose, increasing privacy, compliance, and trust risk.

Description-Behavior Mismatch

High
Confidence
99% confidence
Finding
The manifest describes a single-purpose page-fetching unlocker, but the code builds multi-step business-intelligence workflows for pricing, reviews, leads, and brand monitoring. This mismatch is dangerous because operators and users may authorize or trust the skill under a much narrower data-access model than what the code actually performs.

Intent-Code Divergence

Medium
Confidence
90% confidence
Finding
The module docstring explicitly says this is shared execution for Dataify business skills, contradicting the advertised narrow unlocker purpose. In security review, this kind of internal description mismatch is a strong indicator that the package may be acting as a general orchestrator rather than the limited function users expect.

Description-Behavior Mismatch

High
Confidence
97% confidence
Finding
The module includes explicit search-engine querying support via SERP_ENDPOINT and search(), even though the skill description says it must not be used for search-engine discovery. This creates a capability mismatch that can be abused to enumerate targets or broaden collection beyond a user-supplied URL, undermining policy boundaries and enabling stealthier scraping workflows.

Context-Inappropriate Capability

High
Confidence
95% confidence
Finding
A webpage unlocking skill should accept known URLs and fetch them; adding a general-purpose search primitive materially expands the skill into target discovery. In this context, that extra capability lowers friction for mass reconnaissance and makes it easier to bypass the stated operational limits of the skill.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The skill instructs users to send arbitrary URLs and optionally raw headers/cookies to a third-party API, but it does not clearly warn that these inputs will be transmitted off-platform to an external service. This can lead users to unknowingly disclose sensitive session tokens, internal URLs, or private targets to the provider, especially because the skill markets itself as a general-purpose unlocker for blocked pages.

Missing User Warnings

Medium
Confidence
87% confidence
Finding
The code sends user-provided subjects, queries, and URLs to external Dataify endpoints and related capabilities without any visible per-request disclosure or confirmation in this execution path. In a skill ecosystem, silent transmission of user-supplied targets to third-party services can create privacy, contractual, and compliance exposure, especially given the broader-than-advertised workflow scope.

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