Back to skill

Security audit

Dataify Review Intelligence

Security checks for vulnerabilities and agentic risk

Overview

The skill matches its review-analysis purpose, but it handles a Dataify API token and resumable local state in ways that could expose credentials or local files.

Review before installing. Use this only if you are comfortable giving the skill access to your Dataify API token and allowing paid Dataify collection requests. Avoid running it with resume files you did not create or trust, keep source URLs explicit, monitor credit usage, and consider rotating the Dataify token if it may have been logged in query-string task requests.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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 (3)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/wait_for_task.py:34
Finding
Long-Lived API Token Transmitted in URL Query Parameters<![CDATA[ ## Vulnerability Details **File Location**: `scripts/wait_for_task.py:34-38, 103-108, 130-136` **Duplicated Location**: `_dependencies/skills/dataify-task-operations/scripts/wait_for_task.py` **Vulnerability Type**: Credential exposure through URL query parameters **Risk Level**: Medium ### 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 function is invoked with the API token in `params`: ```python payload = request_json( STATUS_ENDPOINT, {"api_key": api_key, "task_id": task_id}, api_key, request_timeout, ) ``` The same behavior occurs when downloading results: ```python return request_json( DOWNLOAD_ENDPOINT, {"api_key": api_key, "task_id": task_id, "type": "json"}, api_key, request_timeout, ) ``` ### Technical Analysis The polling implementation places `DATAIFY_API_TOKEN` in the query string for both task-status and result-download requests. Although the endpoints use HTTPS, URL query strings can be retained in server access logs, reverse-proxy logs, monitoring systems, network diagnostics, browser-like URL histories, and error reports. The response redaction performed by the function does not protect the request URL: ```python if api_key: text = text.replace(api_key, "<redacted>") ``` This replacement only removes the token if it appears in the response body. It cannot remove copies retained by infrastructure before the response is processed. Sending authentication credentials as an `Authorization` header is the minimum-privilege design already used by other project components. Query-string authentication unnecessarily increases the number of systems that may observe or retain the credential. ### Attack Path 1. A user configures a valid, long-lived `DATAIFY_API_T ...[truncated 1006 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove `api_key` from all query parameters. 2. Send the token exclusively in 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 {}".format(api_key)}, method="GET", ) ``` 3. Keep only non-secret values such as `task_id` and `type` in the URL. 4. Use an explicit redirect policy. Do not forward the authorization header if a redirect changes the scheme, hostname, or port. 5. Redact tokens from exception messages and diagnostic output in addition to response bodies. 6. Rotate any token that may already have appeared in retained access logs. 7. Apply the same correction to the duplicated dependency implementation. ]]>

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
scripts/business_workflow.py:541
Finding
Untrusted Resume State Enables Path Traversal and Arbitrary Local File Access<![CDATA[ ## Vulnerability Details **File Location**: `scripts/business_workflow.py:541-543, 585-589, 624-627, 637-638` **Duplicated Location**: `_dependencies/skills/dataify-task-operations/scripts/business_workflow.py` **Vulnerability Type**: Path traversal through unvalidated persisted workflow state **Risk Level**: Medium ### Vulnerable Code Resume files are loaded without schema or path validation: ```python if args.resume: root = args.resume if args.resume.is_dir() else args.resume.parent state_path = root / "state.json" if args.resume.is_dir() else args.resume state = json.loads(state_path.read_text(encoding="utf-8")) ``` An output path taken directly from the resumed state is subsequently read: ```python for action in state["actions"]: if action["status"] != "success" or not action.get("output"): continue raw_path = root / action["output"] raw = raw_path.read_bytes() ``` Action identifiers from the state are also used in generated filenames: ```python if success: path = raw_dir / "{}-{}.json".format(action["id"], slug(action["subject"])) path.write_text(completed.stdout, encoding="utf-8") action.update(status="success", output=str(path.relative_to(root)), error=None) ``` Persisted output paths are trusted again during detail-action processing: ```python payloads[action["id"]] = decode_json_stream( (root / action["output"]).read_text(encoding="utf-8") ) ``` ### Technical Analysis The `--resume` option treats `state.json` as trusted application state, but the file can be supplied or modified by an external party. Fields such as `action["output"]` and `action["id"]` are not validated before being used in filesystem paths. In Python, joining a trusted directory with a relative path containing `../` does not confine the result to the trusted directory. For example: ```python root / "../../sensitive-file" ``` can resolve outside `root`. An absolute path may also override the intended root when j ...[truncated 2090 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Define and enforce a strict schema for resumed state, including allowed keys, types, statuses, capabilities, and identifier formats. 2. Require action IDs to match a safe pattern such as: ```python if not re.fullmatch(r"[A-Za-z0-9_-]{1,64}", action_id): raise ValueError("Invalid action ID") ``` 3. Reject absolute paths and traversal components in persisted output paths. 4. Resolve every candidate path and verify that it remains under the intended directory: ```python root_resolved = root.resolve() candidate = (root_resolved / action["output"]).resolve() if not candidate.is_relative_to(root_resolved): raise ValueError("Output path escapes the workflow directory") ``` 5. Apply a more restrictive check for raw result files so they must remain under `raw_dir`, not merely under the workflow root. 6. Do not derive output filenames from untrusted persisted fields. Generate filenames from internal counters or validated UUIDs. 7. Consider storing only a logical output identifier in state and reconstructing the actual path internally. 8. Reject symbolic-link escapes by checking resolved paths immediately before file access. 9. Open new output files with exclusive-creation semantics where appropriate to reduce overwrite risks. 10. Apply identical validation to the duplicated dependency implementation. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
_dependencies/skills/scraper-amazon-comment/scripts/submit_amazon_comment.py:83
Finding
Amazon Scraper Submits Arbitrary Unvalidated Target URLs<![CDATA[ ## Vulnerability Details **File Location**: `_dependencies/skills/scraper-amazon-comment/scripts/submit_amazon_comment.py:83-105` **Vulnerability Type**: Missing target URL validation **Risk Level**: Low ### Vulnerable Code ```python parser = argparse.ArgumentParser(description="Submit a Dataify Amazon comment Builder task.") parser.add_argument("--url", required=True, help="Amazon product URL.") parser.add_argument("--file-name", default=DEFAULT_FILE_NAME, help="Builder file_name value. Defaults to {{TasksID}}.") parser.add_argument("--no-wait", action="store_true", help="Return after submission without waiting for the final result.") parser.add_argument("--wait-timeout", type=float, default=600, help="Maximum final-result wait in seconds.") args = parser.parse_args() api_token = os.environ.get("DATAIFY_API_TOKEN", "").strip() if not api_token: print("Missing Dataify API TOKEN. Get one from {}. New accounts get 50 free credits, enough for about 6,000 trial results, valid for 7 days, and only successful requests are billed.".format(DATAIFY_URL), file=sys.stderr) return 2 url = args.url.strip() if not url: print("URL cannot be empty.", file=sys.stderr) return 2 file_name = args.file_name.strip() if not file_name: print("File name cannot be empty.", file=sys.stderr) return 2 try: task_id = submit_builder(api_token, url, file_name) ``` The unchecked value is transmitted as the scraper target: ```python form = { "spider_name": "amazon.com", "spider_id": "amazon_comment_by-url", "spider_parameters": json.dumps([{"url": url}], separators=(",", ":"), ensure_ascii=False), "spider_errors": "true", "file_name": file_name, } ``` ### Technical Analysis The Skill documentation describes this parameter as an Amazon product URL and states that the URL is validated. The implementation only checks whether the string is non-empty. It does not verify: - The URL scheme. - Whether the hostname belongs to an appro ...[truncated 1806 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Parse the supplied URL using `urllib.parse.urlsplit`. 2. Require HTTPS unless there is a documented need for HTTP. 3. Reject embedded usernames and passwords. 4. Normalize the hostname using lowercase and IDNA processing. 5. Maintain an explicit allowlist of supported Amazon domains rather than using substring matching. 6. Reject IP-literal hosts, localhost names, private ranges, loopback addresses, link-local addresses, and nonstandard ports. 7. Require a recognized Amazon product path, such as a validated `/dp/<ASIN>` or equivalent supported format. 8. Validate the ASIN using a restrictive pattern and reject unsupported paths. 9. Perform validation before calling `submit_builder()`. 10. Keep server-side validation enabled as defense in depth. 11. Add tests for deceptive hosts such as `amazon.example.com`, `amazon.com.attacker.example`, embedded credentials, IPv4/IPv6 literals, encoded hostnames, and private-address targets. ]]>
Vulnerability Patterns
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (3)

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill directs execution of a Python script, token handling, environment inspection, shell command selection, and network-backed collection, but it declares no explicit permissions. This creates a trust gap where an operator may approve or run the skill without understanding that it can access environment variables, read/write files, invoke shell commands, and make outbound requests, increasing the chance of unintended data exposure or unsafe execution.

Vague Triggers

Medium
Confidence
90% confidence
Finding
The manifest allows implicit invocation while advertising a broad, generic capability to collect Amazon comment data and return results. This can cause the agent to trigger the scraper without clear user intent or sufficient scoping, increasing the risk of unintended data collection, policy bypass, or use in contexts the user did not explicitly authorize.

Vague Triggers

Medium
Confidence
87% confidence
Finding
The default prompt is broadly phrased and can trigger the skill for loosely related requests about public feedback without clearly constraining when it should be used. Combined with implicit invocation, this increases the chance of overbroad activation, causing the agent to analyze unintended datasets or perform actions outside the user's precise intent.

Static analysis

Detected: suspicious.exposed_secret_literal

File appears to expose a hardcoded API secret or token.

Critical
Code
suspicious.exposed_secret_literal
Location
_dependencies/skills/dataify-task-operations/scripts/task_runtime.py:38

File appears to expose a hardcoded API secret or token.

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