Back to skill

Security audit

Dataify SEO Audit

Security checks for vulnerabilities and agentic risk

Overview

The SEO audit skill is mostly coherent, but it ships under-scoped URL fetching and auxiliary scripts that can mishandle the Dataify API token.

Review before installing. Use it only for public websites you are authorized to audit, keep DATAIFY_API_TOKEN scoped and rotate it if exposed, avoid internal or secret-bearing URLs, and be aware that bundled auxiliary scripts are broader than the documented SEO audit workflow.

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

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/wait_for_task.py:35
Finding
API Token Exposed in URL Query Strings## Vulnerability Details **File Location**: `scripts/wait_for_task.py`, lines 35–39, with vulnerable calls at lines 103–107 and 129–133 **Vulnerability Type**: Sensitive 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 called with the API token inside `params`: ```python payload = request_json( STATUS_ENDPOINT, {"api_key": api_key, "task_id": task_id}, api_key, request_timeout, ) ``` The same pattern is used to download the completed task: ```python return request_json( DOWNLOAD_ENDPOINT, {"api_key": api_key, "task_id": task_id, "type": "json"}, api_key, request_timeout, ) ``` ### Technical Analysis The task poller puts `DATAIFY_API_TOKEN` directly into the query string of requests to `/task_status` and `/download`. HTTPS encrypts the URL while it is in transit, but it does not prevent the complete URL from being recorded at endpoints or intermediaries that terminate or observe the request. Query strings are commonly retained in web-server access logs, reverse-proxy logs, application performance monitoring systems, debugging traces, browser or client diagnostics, and exception telemetry. The following response-body replacement does not protect the request URL: ```python if api_key: text = text.replace(api_key, "<redacted>") ``` This replacement occurs only after receiving a response and therefore cannot remove the credential from infrastructure logs created while processing the request. The behavior is not necessary for the declared functionality. Other project clients already authenticate safely with an `Authorization: Bearer ...` header. ### Attack Path 1. A user configures a valid `DA ...[truncated 1087 chars]
Remediation
## Remediation Suggestions - Remove `api_key` from all query parameter dictionaries. - Send the credential through an HTTP 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", ) ``` - If the API does not support bearer authentication for these endpoints, use a POST body rather than the URL and update the service contract accordingly. - Keep only non-secret values such as `task_id` and `type` in the query string. - Apply centralized URL and header redaction to HTTP diagnostics, exceptions, tracing, and telemetry. - Rotate tokens that may already have appeared in request logs. - Configure server, proxy, and monitoring systems not to record authorization headers or sensitive query parameters.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/dataify_client.py:34
Finding
Public-URL Validation Accepts Private, Loopback, and Metadata Destinations## Vulnerability Details **File Location**: `scripts/dataify_client.py`, lines 34–41; used by `scripts/run_seo_audit.py`, lines 202–216 **Vulnerability Type**: Insufficient URL validation enabling SSRF-style remote fetches **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 subsequently sent to the remote fetch 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) ``` The main SEO workflow relies on this validation: ```python base = normalize_url(args.url) ``` ### Technical Analysis The function claims to require a public HTTP(S) URL, but it validates only URL syntax. It does not resolve the hostname or reject non-public address ranges. Consequently, inputs such as the following satisfy the current checks: ```text http://127.0.0.1/ http://localhost/ http://10.0.0.1/ http://192.168.1.1/ http://169.254.169.254/ ``` The URL is forwarded to `https://webunlocker.dataify.com/request`, and the payload enables redirects through `"follow_redirect": "True"`. If the external fetch service does not independently enforce destination restrictions, an attacker could target services reachable from its ...[truncated 2060 chars]
Remediation
## Remediation Suggestions - Resolve the hostname before submitting the request and reject every resolved address that is loopback, private, link-local, multicast, reserved, or unspecified. - Use Python’s `ipaddress` module for classification: ```python import ipaddress import socket def require_public_host(hostname: str) -> None: if hostname.casefold() == "localhost": raise ValueError("Local destinations are not allowed") for result in socket.getaddrinfo(hostname, None, type=socket.SOCK_STREAM): address = ipaddress.ip_address(result[4][0]) if not address.is_global: raise ValueError("Only globally routable destinations are allowed") ``` - Parse and validate `parts.hostname` separately from the port instead of applying a regular expression to the complete `netloc`. - Explicitly block cloud metadata hostnames and well-known metadata addresses as defense in depth. - Enforce the same restrictions in the Dataify Web Unlocker, where the actual connection occurs. - Revalidate every redirect target before following it, or disable redirects and process each redirect explicitly. - Mitigate DNS rebinding by connecting only to a validated resolved address while preserving the intended host for TLS and HTTP semantics. - Apply equivalent validation to sitemap URLs and all dynamically discovered detail URLs. - Add regression tests covering IPv4, IPv6, encoded addresses, mixed-case hostnames, trailing dots, redirects, and DNS rebinding scenarios.
Vulnerability Patterns
  • 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
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (2)

Lp3

Medium
Category
MCP Least Privilege
Confidence
83% confidence
Finding
The skill instructs the agent to use shell execution, network access, environment-variable handling, and file read/write, but it does not declare any permissions or capability boundaries. That creates a real security and governance issue because users and enforcement layers cannot accurately assess or constrain what the skill may do before execution.

Missing User Warnings

Medium
Confidence
87% confidence
Finding
In preview mode, the script prints a fully populated curl command including all submitted parameters. If users pass sensitive targets or parameters, those values can be exposed in terminal history, logs, screenshots, or CI output even though the script provides no warning or redaction. In this SEO-audit skill, inputs are usually URLs and page metadata targets, so the risk is moderate rather than severe, but secrets or internal URLs could still be leaked if operators misuse it.

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