Back to skill

Security audit

Dataify Task Operations

Security checks for vulnerabilities and agentic risk

Overview

The skill's advertised task-monitoring purpose is mostly coherent, but the package also contains broader unadvertised Dataify submission and business-intelligence collection code plus unsafe generated shell commands.

Review before installing. The core monitoring flow is reasonable for Dataify tasks, but this package includes additional undisclosed collection/submission code and generated shell-command paths with injection risks. Use only with task IDs and parameters you trust, avoid copying preview or resume commands from untrusted input, and prefer a version that validates task IDs, safely quotes generated commands, and documents all bundled Dataify workflows.

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

Warning
Location
scripts/wait_for_task.py:33
Finding
API Token Exposed in URL Query Parameters<![CDATA[ ## Vulnerability Details **File Location**: `scripts/wait_for_task.py`, lines 33–66, 97–105, and 124–132 **Vulnerability Type**: Credential exposure through URL query strings **Risk Level**: Medium **Category**: T09: Insecure Skill Coding Practices ### 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: content = response.read() charset = response.headers.get_content_charset() or "utf-8" text = content.decode(charset, errors="replace") except urllib.error.HTTPError as exc: detail = exc.read().decode("utf-8", errors="replace") normalized = detail.lower() if "credit" in normalized or "balance" in normalized or "余额" in detail or "积分" in detail: raise RuntimeError( "Dataify account has insufficient credits. Review balance or recharge at {}.".format(ACCOUNT_URL) ) if exc.code in (401, 403): raise RuntimeError( "DATAIFY_API_TOKEN was rejected. Review or rotate the API key at {}; a new registration is not required.".format( ACCOUNT_URL ) ) raise RuntimeError(detail or "HTTP {}".format(exc.code)) except urllib.error.URLError as exc: raise RuntimeError("Request failed: {}".format(exc.reason)) if api_key: text = text.replace(api_key, "<redacted>") try: return json.loads(text) except ValueError: raise RuntimeError("Dataify returned a non-JSON response") ``` The API token is supplied to this function as a query parameter: ```python payload = request_json( STATUS_ENDPOINT, {"api_key": api_key, "task_id": task_id}, api_key, request_timeout, ) ``` The same behavior occurs when downl ...[truncated 2045 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Move the API token into an authorization header: ```python request = urllib.request.Request( url, headers={"Authorization": "Bearer {}".format(api_key)}, method="GET", ) ``` 2. Keep only non-secret values such as `task_id` and `type` in the query string. 3. If the Dataify API currently requires `api_key` in the URL, request or adopt a header-based authentication endpoint before distributing the Skill. 4. Configure server, proxy, and application logging to redact legacy `api_key` parameters. 5. Ensure exceptions and diagnostic output never include request headers or complete URLs containing credentials. 6. Rotate tokens that may already have appeared in request logs and review associated account usage. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/wait_for_task.py:92
Finding
Command Injection Through Unvalidated Task IDs in Resume Commands<![CDATA[ ## Vulnerability Details **File Location**: `scripts/wait_for_task.py`, lines 92–94 and 179–226; `scripts/task_runtime.py`, lines 40–51 **Vulnerability Type**: Shell command injection in generated resume instructions **Risk Level**: High **Category**: T09: Insecure Skill Coding Practices ### Vulnerable Code `scripts/wait_for_task.py` constructs a shell command by placing the task ID directly inside double quotes: ```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)) ``` The task ID is stripped but not restricted to a safe character set: ```python task_id = str(args.task_id).strip() if not task_id: print("task-id cannot be empty", file=sys.stderr) return 2 ``` The generated command is presented on timeout or interruption: ```python except KeyboardInterrupt: print( "Monitoring interrupted. Do not resubmit the task.\nResume: {}".format( resume_command(task_id, args.timeout) ), file=sys.stderr, ) return 130 except TimeoutError as exc: print( "{}\nResume: {}".format(str(exc), resume_command(task_id, args.timeout)), file=sys.stderr, ) return 3 ``` The same unsafe construction appears in `scripts/task_runtime.py`: ```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)) ) ``` ### Technical Analysis Double quotes are not a sufficient shell-esc ...[truncated 1914 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Validate every task ID before any network request or command generation: ```python if not re.fullmatch(r"[A-Za-z0-9_-]{8,128}", task_id): raise ValueError("Invalid task ID format") ``` 2. Centralize validation so CLI-provided IDs and IDs extracted from API responses use the same rules. 3. For POSIX display commands, quote each argument using `shlex.quote()`: ```python command = " ".join( shlex.quote(value) for value in [ "python3", waiter, "--task-id", task_id, "--timeout", str(int(float(wait_timeout))), ] ) ``` 4. Use a platform-appropriate quoting mechanism on Windows rather than reusing POSIX quoting. 5. If the system executes a resume operation internally, use an argument array with `subprocess.run(..., shell=False)` and never pass the generated string to a shell. 6. Treat task IDs received from users, conversations, URLs, and provider responses as untrusted input. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/catalog_builder.py:83
Finding
Command Injection in Generated Preview Curl Command<![CDATA[ ## Vulnerability Details **File Location**: `scripts/catalog_builder.py`, lines 83–93 and 136–143 **Vulnerability Type**: Shell command injection through unsafe command generation **Risk Level**: High **Category**: T09: Insecure Skill Coding Practices ### Vulnerable Code The preview command embeds JSON containing user-controlled parameter values inside a single-quoted shell argument: ```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 JSON is derived from command-line input or a user-supplied file and is printed as executable shell text: ```python rows = map_select_labels(tool, load_rows(args.values_file, args.params_json)) validate_required(tool, rows) ``` ```python payload_json = json.dumps(rows, ensure_ascii=False, separators=(",", ":")) if args.preview: print(build_curl(tool, payload_json)) return 0 ``` ### Technical Analysis JSON strings can legitimately contain apostrophes. Because `spider_parameters_json` is placed inside a shell single-quoted string without escaping, an apostrophe in a supplied parameter terminates the intended quoting context. Subsequent shell metacharacters can then be interpreted as commands. The `--preview` path does not execute the command itself. However, it deliberately generates a curl command for users or agents to copy and execute, so untrusted data is converted into an executable command without safe encoding. The authorization header also uses single quotes around `$DATAIFY_API_TOKEN`, which prevents ordinary POSIX shell ...[truncated 1258 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Prefer structured preview output rather than executable shell text. For example, print the endpoint, headers with redacted credentials, and form fields as JSON. 2. If a shell command is required, apply `shlex.quote()` independently to every dynamic argument: ```python import shlex parts = [ "curl", "-X", "POST", 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}}", ] preview = " ".join(shlex.quote(part) for part in parts) ``` 3. Clearly label preview output as data rather than a trusted command when it contains user-supplied values. 4. Correct token expansion without exposing the token. If shell output remains supported, avoid quoting that accidentally turns `$DATAIFY_API_TOKEN` into a literal value while still preserving safe argument boundaries. 5. Add tests covering apostrophes, newlines, command substitutions, semicolons, and other shell metacharacters in catalog parameters. ]]>
Vulnerability Patterns
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • 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)

Lp3

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding
The skill exercises sensitive capabilities including environment access, shell execution, file operations, and network use, yet declares no permissions boundary. That creates a governance and review gap: users and the platform cannot accurately assess what the skill may access or do before activation, increasing the chance of unintended secret access or external actions.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The declared purpose says the skill only monitors existing tasks or helps with token setup, but the behavior evidently also includes task submission, external scraping/API calls, and report generation. This mismatch is dangerous because users or orchestration logic may invoke the skill under a lower-risk assumption while it performs broader, cost-bearing, and data-collecting actions than advertised.

Description-Behavior Mismatch

High
Confidence
98% confidence
Finding
The file implements broad business-intelligence collection and analysis workflows that materially exceed the declared purpose of a task-operations skill limited to monitoring/recovery/token guidance. This scope mismatch is dangerous because it enables unannounced external collection, scraping, and reporting behavior under a misleading skill identity, increasing the chance of unauthorized data gathering and user deception.

Context-Inappropriate Capability

High
Confidence
99% confidence
Finding
The code can invoke multiple unrelated search and scraping capabilities, including Google search/news/shopping and web unlocker/scraper skills, based on generated actions. In the context of a task-operations skill, this is dangerous because it creates a hidden broker that can trigger broad external collection outside the user's likely expectation or the skill's stated authorization boundary.

Intent-Code Divergence

Medium
Confidence
90% confidence
Finding
The module docstring explicitly says the code is shared execution logic for Dataify business skills, which contradicts the advertised task-operations-only purpose. This mismatch is a security concern because misleading packaging and documentation can conceal actual capabilities, undermining review, consent, and policy enforcement.

Vague Triggers

Medium
Confidence
83% confidence
Finding
The activation language is broad enough to auto-trigger on common requests such as scraper submission follow-ups, existing task references, or token setup questions. In a skill with shell, env, network, and result-download behavior, over-broad triggering increases the risk of unintended execution, accidental external calls, or exposure to task data in contexts where the user did not clearly request this skill.

Missing User Warnings

Medium
Confidence
83% confidence
Finding
Preview mode prints a fully formed curl command that includes the user-supplied spider_parameters payload. Those parameters may contain sensitive target data, and printing them to stdout can expose them to shell history, terminal logs, CI logs, or agent transcripts. In this skill context, preview output is especially risky because the tool is designed to handle task submissions automatically, increasing the chance that verbose output is captured elsewhere.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
- Show a prominent Dataify account CTA only when the API token is missing, rejected/invalid, or the account has insufficient credits.
- For a missing token, offer https://dashboard.dataify.com/login?utm_source=skill and state: New accounts get 50 free credits, enough for about 6,000 trial results, valid for 7 days, and only successful requests are billed. Never ask the user to paste the token into chat.
- Detect the current operating system and shell. Show only the matching session-scoped setup command first (`export` for macOS/Linux shells, `$env:` for Windows PowerShell, or `set` for Windows Command Prompt). Show other platforms or persistent setup only when detection is ambiguous or the user asks.
- After the user says the token is configured, verify only whether `DATAIFY_API_TOKEN` is present; never print its value. If verification succeeds, continue the original task without asking the user to repeat it.
- Explain that persistent shell changes may require a new terminal or restarting the agent application. Do not recommend a project `.env` unless the execution path explicitly loads it, and ensure `.env` is ignored by version control.
- For an invalid token, direct the user to API-key management without implying that a new registration is required. For insufficient credits, direct the user to balance or recharge management.
- During normal submission, processing, and successful completion, do not promote registration or the Dashboard. Never expose the token or include it in CTA attribution parameters.
Confidence
81% confidence
Finding
Automatically continuing the original task after token verification without asking the user to repeat it can cause the agent to resume networked, potentially billable operations based on prior context rather than a fresh authorization moment. In this skill's context, where actual behavior may include submission and retrieval workflows, this increases the risk of unintended external actions and charges.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
2. Read `DATAIFY_API_TOKEN` from the environment. Never accept or print it as a command-line argument. If it is missing, follow [Token setup](references/token-setup.md), show only the current platform's setup command, and resume this workflow after safe verification.
3. Use an installed Dataify task-status/result tool when available. Do not invent an endpoint that is not documented in the repository or exposed by a connected tool.
4. After a scraper submission, monitor by default with `scripts/wait_for_task.py`. Stop at submission only when the user explicitly asks for a task ID or `--no-wait` behavior.
5. If status is queued or running, continue bounded monitoring. Do not ask the user to request monitoring separately.
6. If succeeded, download and return the available result. Summarize large results and preserve access to raw data.
7. If failed, return the provider error, likely corrective action, and whether retrying is safe.
8. If monitoring times out or is interrupted, report the task ID and exact resume command. Never resubmit merely because monitoring stopped.
Confidence
84% confidence
Finding
The workflow instructs the agent to continue monitoring automatically and not ask the user to request monitoring separately, while also reading a token from the environment and downloading results on success. For a skill with external network access and possible cost or sensitive result retrieval, removing a confirmation checkpoint can lead to unintended actions or automatic access to data the user did not expect to fetch immediately.

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