Back to skill

Security audit

Dataify Google News

Security checks for vulnerabilities and agentic risk

Overview

This Google News skill mostly does the advertised search task, but the package also contains broader data-collection workflows and credential-handling patterns that users should review before installing.

Install only if you are comfortable with a Dataify skill package that contains more than Google News search. Prefer session-scoped DATAIFY_API_TOKEN setup, do not pass tokens on the command line, avoid executing generated preview or resume shell commands without inspection, and treat the bundled business workflow scripts as off-scope for the advertised skill.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/wait_for_task.py:105
Finding
API Credential Exposed in URL Query Parameters<![CDATA[ ## Vulnerability Details **File Location**: `scripts/wait_for_task.py:105-110, 128-133` **Vulnerability Type**: Credential exposure through URL query strings **Risk Level**: High ### Vulnerable Code ```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, ) ``` The called function constructs the URL directly from these parameters: ```python def request_json(endpoint, params, api_key, timeout): url = endpoint + "?" + urllib.parse.urlencode(params) request = urllib.request.Request(url, method="GET") ``` ### Technical Analysis The API credential is included as the `api_key` query parameter in requests to the task-status and result-download endpoints. Although HTTPS protects the request in transit, URL query strings are routinely captured by reverse-proxy access logs, web server logs, application monitoring, exception telemetry, and debugging tools. The later response-body redaction: ```python if api_key: text = text.replace(api_key, "<redacted>") ``` does not protect the request URL and therefore does not mitigate this exposure. ### Attack Path 1. A user configures `DATAIFY_API_TOKEN`. 2. The task waiter polls the status endpoint or downloads a completed result. 3. The token is transmitted in a URL such as `...?api_key=<token>&task_id=<id>`. 4. Dataify infrastructure, a reverse proxy, monitoring system, or debugging tool records the complete URL. 5. A party with access to those records extracts the token. 6. The recovered credential is reused to consume account credits or access task results. ### Impact Assessment An exposed token may allow unauthorized use of the victim’s Dataify account, consumption of paid credits, and access to task status or downloaded results available to that credential. Exp ...[truncated 164 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Remove `api_key` from all URL query parameters. - Authenticate using an HTTP header: ```python request = urllib.request.Request( url, headers={"Authorization": "Bearer {}".format(api_key)}, method="GET", ) ``` - Update both task-status and download requests to use the header-based mechanism. - If the remote API only supports query-string authentication, use short-lived, task-scoped credentials and explicitly disable URL logging at every intermediary. - Add tests that assert the token never appears in constructed URLs, logs, exceptions, progress output, or resume instructions. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/google_news.py:301
Finding
API Token Accepted Through Process Arguments and Rewritten Into the Environment<![CDATA[ ## Vulnerability Details **File Location**: `scripts/google_news.py:99, 301-308` **Vulnerability Type**: Unsafe local credential handling **Risk Level**: Medium ### Vulnerable Code ```python parser.add_argument("--token", help="Dataify API token. Bearer prefix is optional.") ``` ```python def get_authorization(token_arg: str | None) -> str | None: token = clean_value(token_arg) or clean_value(os.environ.get("DATAIFY_API_TOKEN")) if not token: return None if not token.lower().startswith("bearer "): token = f"Bearer {token}" os.environ["DATAIFY_API_TOKEN"] = token return token ``` ### Technical Analysis The `--token` option allows a secret to be supplied on the command line. Command-line arguments can be exposed through shell history, process inspection tools, audit systems, terminal logging, and diagnostic reports. The function then copies the normalized bearer credential into `os.environ`. This mutation is unnecessary because the function already returns the authorization value. Environment variables are inherited by child processes by default and may also be captured by crash reports or diagnostics. Storing the `Bearer `-prefixed value in the environment additionally changes the format of the original variable and broadens its lifetime within the process tree. ### Attack Path 1. A user invokes the script with `--token <secret>`. 2. The command, including the secret, is retained in shell history or exposed through process inspection. 3. A local user or monitoring process reads the command line and recovers the token. 4. Alternatively, the script writes the normalized credential into `os.environ`. 5. A subsequently launched child process or diagnostic collector reads the inherited environment. 6. The recovered token is reused against Dataify services. ### Impact Assessment Exploitation requires local access, access to command telemetry, or control over a child process. A recovered token can permit unauthor ...[truncated 222 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Remove the `--token` command-line option. - Read the token from a protected environment variable, operating-system keychain, or narrowly scoped secret manager. - Do not write the normalized value back into `os.environ`. - Keep the bearer value in a local variable for only as long as required: ```python def get_authorization() -> str | None: token = clean_value(os.environ.get("DATAIFY_API_TOKEN")) if not token: return None token = token.removeprefix("Bearer ").strip() return f"Bearer {token}" ``` - If child processes are ever launched, provide a minimal explicit environment and remove sensitive variables unless the child requires them. - Ensure errors and debugging output never print request headers or token values. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
SKILL.zh-CN.md:21
Finding
Localized Documentation Recommends Plaintext Persistent Token Storage<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.zh-CN.md:21-47` **Vulnerability Type**: Insecure persistent credential storage **Risk Level**: Medium ### Vulnerable Code ```powershell [Environment]::SetEnvironmentVariable("DATAIFY_API_TOKEN", "your_token_here", "User") ``` ```bash echo 'export DATAIFY_API_TOKEN="your_token_here"' >> ~/.bashrc source ~/.bashrc ``` ```bash echo 'export DATAIFY_API_TOKEN="your_token_here"' >> ~/.zshrc source ~/.zshrc ``` ### Technical Analysis The localized documentation explicitly recommends permanent environment-variable configuration. On Unix-like systems, this writes the token in plaintext to shell startup files and exports it to every descendant process started from those shells. On Windows, the token is stored as a persistent user environment variable and becomes broadly available to processes running under that account. These instructions also conflict with the later policy in the same document, which says session-scoped setup should be shown first and persistent setup should only be provided when requested. The detected operations are credential-persistence instructions; they do not install SSH keys, scheduled tasks, services, or executable backdoors. ### Attack Path 1. A user follows the localized setup guide. 2. The API token is stored in a shell startup file or persistent user environment. 3. An unrelated process running as that user reads the inherited environment or startup file. 4. Backups, synchronization tools, diagnostics, or accidental file disclosure may also copy the plaintext token. 5. The exposed token is reused against Dataify APIs. ### Impact Assessment Any process operating with the user’s normal account privileges may gain access to the credential. Compromise can persist across terminal and application restarts until the token is removed or rotated. The primary impact is unauthorized Dataify API access and account-credit consumption rather than operating-system persistence by mali ...[truncated 15 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Remove the recommendation to permanently store tokens in `.bashrc`, `.zshrc`, or the Windows user environment. - Make session-scoped commands the default in every localized document. - Prefer an operating-system keychain or dedicated secret manager for persistent storage. - Only document persistent storage as an explicit opt-in requested by the user. - If file-based persistence must be documented, include strict file-permission, backup-exclusion, process-inheritance, and token-rotation guidance. - Keep the English and localized specifications synchronized so that both enforce the same credential policy. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/catalog_builder.py:87
Finding
Shell Injection in Generated Catalog Preview Command<![CDATA[ ## Vulnerability Details **File Location**: `scripts/catalog_builder.py:87-97` **Vulnerability Type**: Command injection through unsafe shell-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}}'", ]) ``` ### Technical Analysis The generated curl command places `spider_parameters_json` inside single quotes without escaping embedded apostrophes. Catalog values originate from `--params-json` or a user-provided values file and can therefore contain attacker-controlled strings. An apostrophe in the JSON data can terminate the shell’s quoted argument. Shell metacharacters placed after that apostrophe can append arbitrary commands. The function returns output formatted as directly executable curl syntax, making copy-and-execute use foreseeable. ### Attack Path 1. An attacker supplies or persuades a user to process catalog JSON containing an apostrophe followed by shell operators and a command. 2. The user runs the catalog builder with `--preview`. 3. `build_curl()` inserts the attacker-controlled JSON into a single-quoted shell command without escaping it. 4. The generated preview closes the original quote and introduces the attacker’s shell command. 5. A user or agent copies and executes the generated curl command. 6. The injected command runs with the privileges of that user or agent process. ### Impact Assessment Successful exploitation permits arbitrary local command execution under the account that executes the generated pr ...[truncated 310 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Do not generate executable shell commands from untrusted request data. - Prefer printing a structured, redacted JSON preview rather than curl syntax. - If shell output is unavoidable, quote every independently generated shell argument using `shlex.quote()` on POSIX systems. - Do not attempt manual character replacement; use a standard argument-quoting implementation. - Clearly label preview output as non-executable and omit authorization material. - Add tests with apostrophes, newlines, command substitutions, semicolons, pipes, and shell redirection characters. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/task_runtime.py:18
Finding
Unvalidated Task IDs Embedded in Executable Resume Commands<![CDATA[ ## Vulnerability Details **File Location**: `scripts/task_runtime.py:18-22, 43-46`; `scripts/wait_for_task.py:95-97` **Vulnerability Type**: Command injection through unsafe command rendering **Risk Level**: Medium ### Vulnerable Code Task IDs extracted from dictionary responses are accepted without the validation applied to plain-string responses: ```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 unvalidated identifier is then embedded in a shell command: ```python command = 'python3 "{}" --task-id "{}" --timeout {}'.format( waiter, task_id, int(float(wait_timeout)) ) ``` The standalone waiter uses the same unsafe pattern: ```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 `extract_task_id()` applies the allowlist `[A-Za-z0-9_-]{8,128}` only when the entire response is a string. Values found under `task_id` or `taskId` in a dictionary are converted to strings without validation. On timeout or interruption, the identifier is interpolated between double quotes in a command presented as a resumable invocation. A malicious identifier containing a double quote and shell metacharacters can break out of the quoted argument. The command is printed rather than executed directly, so exploitation requires a user or agent to execute the supplied resume instruction. ### Attack Path 1. A malicious or compromised API endpoint returns a crafted `task_id` field containing quote and shell syntax. 2. `extract_task_id()` accepts the value because dictionary-derived identifiers are not validated. 3. Task monitoring times out or is interrupt ...[truncated 619 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Validate every task ID, regardless of whether it came from a string or dictionary: ```python candidate = str(value).strip() if re.fullmatch(r"[A-Za-z0-9_-]{8,128}", candidate): return candidate return None ``` - Apply the same validation in `wait_for_task.py` before network requests or command rendering. - Prefer displaying resume arguments as a structured array rather than an executable shell string. - If a shell command must be displayed, use platform-appropriate argument quoting such as `shlex.quote()` on POSIX systems. - Add tests for quotes, spaces, newlines, semicolons, command substitutions, and redirection characters in API-provided task IDs. ]]>

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
scripts/business_workflow.py:25
Finding
Bundled Auxiliary Workflows Exceed the Declared Google News Scope<![CDATA[ ## Vulnerability Details **File Location**: `scripts/business_workflow.py:25-46, 84-183, 573-645` **Vulnerability Type**: Excessive capability and least-privilege violation **Risk Level**: Medium ### Vulnerable Code The script defines workflows unrelated to Google News: ```python CONFIG = { "price": { "title": "Price Intelligence", "input_flag": "--product", "input_help": "Product or service to compare.", }, "review": { "title": "Review Intelligence", "input_flag": "--subject", "input_help": "Product, brand, app, or place whose reviews should be analyzed.", }, "lead": { "title": "Lead Intelligence", "input_flag": "--ideal-customer-profile", "input_help": "Ideal customer profile or target-company description.", }, "brand": { "title": "Brand Monitoring", "input_flag": "--brand", "input_help": "Brand to monitor.", }, } ``` It can invoke unrelated sibling scrapers or a web-unlocking capability: ```python if capability == "scraper-amazon-comment": return [sys.executable, str(ROOT / "skills/scraper-amazon-comment/scripts/submit_amazon_comment.py"), "--url", action["url"]] if capability == "scraper-google-maps-reviews": return [sys.executable, str(ROOT / "skills/scraper-google-maps-reviews/scripts/google_maps_reviews.py"), "--url", action["url"]] return [sys.executable, str(ROOT / "skills/dataify-web-unlocker/scripts/invoke-dataify-web-unlocker.py"), "--url", action["url"], "--clean-content", "true"] ``` When sibling tools are unavailable, it directly submits search queries or target URLs to broader Dataify services: ```python request = urllib.request.Request( "https://webunlocker.dataify.com/request", data=json.dumps(payload).encode("utf-8"), headers={"Authorization": "Bearer {}".format(token), "Content-Type": "application/json"}, method="POST", ) ``` It also writes raw responses and workflo ...[truncated 2009 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Remove `business_workflow.py` and other unrelated generic workflow helpers from the Google News Skill package. - Package price, review, lead, brand-monitoring, and web-unlocking features as separately declared skills with independent permission and privacy reviews. - Require explicit user authorization before transmitting source URLs or retaining raw content. - Restrict allowed external capabilities to the fixed Google News endpoint in this package. - If local evidence retention is necessary, document it, use a user-approved output directory, enforce restrictive permissions, and provide deletion controls. - Avoid dynamically invoking sibling skills unless the capability is explicitly declared and authorized. - Add automated packaging checks that reject scripts or endpoints outside the Skill’s documented capability allowlist. ]]>
Vulnerability Patterns
  • 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
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
Findings (7)

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill directs the agent to use shell execution, environment variables, file paths, and outbound network access, but no permissions are declared. That creates a transparency and policy-enforcement gap: a host may allow broader execution than users or reviewers expect, and the skill can invoke external APIs and local scripts without an explicit least-privilege contract.

Description-Behavior Mismatch

High
Confidence
98% confidence
Finding
The file implements broad business-intelligence workflows for price, review, lead, and brand monitoring even though the declared skill is narrowly described as Google News search only. This scope mismatch is dangerous because it gives the skill materially more collection and processing power than users and reviewers would expect, increasing the chance of unauthorized data gathering and policy bypass through a misleading package identity.

Context-Inappropriate Capability

High
Confidence
99% confidence
Finding
The code can generate URL-based actions and route them to general web unlocking or specialized scrapers for arbitrary user-supplied --source-url values. In the context of a Google News skill, that is dangerous privilege expansion: it turns a search utility into a generic fetch-and-scrape tool that can collect data from arbitrary destinations beyond the advertised scope.

Context-Inappropriate Capability

High
Confidence
99% confidence
Finding
The direct API fallback can perform google_search, google_shopping, google_news, and full webunlocker requests, so even without local scripts installed the module retains broad off-manifest collection ability. This materially increases risk because the code preserves hidden capability expansion through remote APIs, making the Google News label misleading and reducing containment expectations.

Intent-Code Divergence

Medium
Confidence
95% confidence
Finding
The module docstring explicitly states it is shared execution for Dataify business skills, which contradicts the narrow Google News-only skill description. That mismatch is a strong indicator of repackaged multi-purpose code and is risky because it obscures true behavior during review and can hide excess privileges behind an innocuous manifest.

Description-Behavior Mismatch

Medium
Confidence
94% confidence
Finding
The `search` function uses Dataify's general Google engine with an arbitrary query and only an optional country code, but does not constrain results to Google News despite the skill being described as Google News-only. This capability mismatch expands the skill beyond its declared scope and can be used to perform unrestricted web search through the provider, which weakens user expectations, policy controls, and downstream safety assumptions.

Context-Inappropriate Capability

High
Confidence
98% confidence
Finding
The `unlock` function allows fetching and rendering arbitrary public URLs, following redirects and returning page content, which is unrelated to a Google News search skill. In context, this turns a narrow search helper into a general-purpose remote content retrieval tool that can be abused to access, transform, or relay arbitrary web content via a third-party unlocking service, bypassing intended scope restrictions.

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