Back to skill

Security audit

amazon-market-trend-scanner

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly fits market-trend scanning, but it needs Review because its API-key handling and optional diagnostics are broader than the main workflow.

Install only if you are comfortable giving this skill a ZooData API key, sending category and product-research queries to ZooData, and retaining local scan state. Prefer the environment variable for the API key, avoid setting ZOODATA_BASE_URL unless you fully control the endpoint, do not use check endpoint probes unless you intend broader diagnostics and credit use, and delete scan-data when you no longer want monitoring history retained.

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/zoodata.py:338
Finding
Bearer Credential Can Be Forwarded to an Untrusted Redirect Destination<![CDATA[ ## Vulnerability Details **File Location**: `scripts/zoodata.py:338-378` **Vulnerability Type**: Improper authorization-header handling across HTTP redirects **Risk Level**: Medium ### Vulnerable Code ```python def api_call(endpoint: str, params: dict) -> dict: """ Make a POST request to ZooData API with retry and error handling. Returns the parsed JSON response on success, with _query metadata injected. Exits with a clear error message on failure. """ global _last_request_time url = f"{BASE_URL}/{endpoint}" if not BASE_URL_TRUSTED: print(f"ERROR: refusing to send your API key to untrusted host '{_host_of(BASE_URL)}'. " "Set ZOODATA_BASE_URL to a zoodata.ai host or localhost, or unset it.", file=sys.stderr) sys.exit(1) api_key = get_api_key() # Clean params: remove None values params = {k: v for k, v in params.items() if v is not None} # Quirk: topN and newProductPeriod must be strings for str_field in ("topN", "newProductPeriod"): if str_field in params and not isinstance(params[str_field], str): params[str_field] = str(params[str_field]) # Save the actual params sent to API (for _query metadata) actual_params = dict(params) body = json.dumps(params).encode("utf-8") headers = { "Authorization": f"Bearer {api_key}", "Content-Type": "application/json", "User-Agent": "ZooData-CLI/1.0 (Python)", } # Rate-limit pacing: enforce minimum interval between requests now = time.monotonic() elapsed = now - _last_request_time if elapsed < MIN_REQUEST_INTERVAL: time.sleep(MIN_REQUEST_INTERVAL - elapsed) delay = RETRY_DELAY max_attempts = MAX_RETRIES for attempt in range(1, max(MAX_RETRIES, RATE_LIMIT_RETRIES) + 1): _last_request_time = time.monotonic() try: req = urllib.request.Request(url, data=body, headers=headers, method="POST") ...[truncated 2060 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Disable automatic redirects for authenticated API requests unless redirects are explicitly required. 2. Implement a custom `HTTPRedirectHandler` that: - Parses and validates every redirect target. - Rejects redirects to non-HTTPS destinations. - Rejects cross-origin redirects or removes `Authorization` before following them. - Applies the same normalized-origin allowlist to every redirect hop. 3. Compare the complete origin—scheme, normalized hostname, and effective port—rather than only the hostname. 4. Prefer rejecting redirects from API endpoints entirely, because a stable API base URL should not normally require them. 5. Add automated tests covering: - Same-origin HTTPS redirects. - Cross-origin redirects. - Redirects from HTTPS to HTTP. - Redirects to loopback and non-ZooData hosts. - Verification that no bearer header reaches a rejected destination. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/zoodata.py:63
Finding
Configured Trusted Hosts May Receive the Bearer Credential over Plaintext HTTP<![CDATA[ ## Vulnerability Details **File Location**: `scripts/zoodata.py:63-85, 338-378` **Vulnerability Type**: Plaintext transmission of a sensitive bearer credential **Risk Level**: Medium ### Vulnerable Code ```python def _is_trusted_host(url): """True only for ZooData hosts and localhost — the sole destinations the API key (Bearer token) may be sent to. Any other host is untrusted and the key is withheld (see api_call), so credentials never reach an arbitrary host.""" host = _host_of(url) return host == "zoodata.ai" or host.endswith(".zoodata.ai") or host in ("localhost", "127.0.0.1") def _resolve_base_url(): """Resolve API base URL, allowing zoodata.ai / localhost hosts via ZOODATA_BASE_URL.""" configured = os.environ.get("ZOODATA_BASE_URL", DEFAULT_BASE_URL).strip().rstrip("/") if configured.rstrip("/") != DEFAULT_BASE_URL.rstrip("/") and not _is_trusted_host(configured): print(f"WARNING: ZOODATA_BASE_URL points at untrusted host '{_host_of(configured)}'. " "Your API key (Bearer token) will NOT be sent there — requests to untrusted " "hosts are refused. Use a zoodata.ai host or localhost.", file=sys.stderr) if configured.endswith(API_BASE_PATH): return configured return f"{configured}{API_BASE_PATH}" BASE_URL = _resolve_base_url() BASE_URL_TRUSTED = _is_trusted_host(BASE_URL) ``` The credential is subsequently attached without a scheme check: ```python headers = { "Authorization": f"Bearer {api_key}", "Content-Type": "application/json", "User-Agent": "ZooData-CLI/1.0 (Python)", } req = urllib.request.Request(url, data=body, headers=headers, method="POST") with urllib.request.urlopen(req, timeout=REQUEST_TIMEOUT) as resp: ``` ### Technical Analysis `_is_trusted_host()` validates only the hostname. It does not require the URL scheme to be HTTPS. Therefore, configurations such as the following pass the trust check: ```text http://api.z ...[truncated 1567 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Parse the configured URL with `urllib.parse.urlparse()` and validate both scheme and hostname. 2. Require `https` for every non-loopback destination. 3. Reject URLs containing embedded user information, malformed authority components, or unexpected schemes. 4. For loopback development: - Prefer HTTPS with a trusted development certificate. - Otherwise require an explicit opt-in flag such as `ZOODATA_ALLOW_INSECURE_LOCALHOST=1`. - Display a prominent warning. - Recommend a non-production or restricted test credential. 5. Compare a normalized origin tuple such as `(scheme, hostname, effective_port)` instead of treating hostname alone as the trust boundary. 6. Add tests proving that `http://api.zoodata.ai` and other plaintext remote URLs fail closed before credential resolution. ]]>

T05 · Unauthorized Access and Privilege Escalation

Note
Location
scripts/zoodata.py:2952
Finding
Allowed Check Command Can Invoke API Endpoints Outside the Skill's Declared Scope<![CDATA[ ## Vulnerability Details **File Location**: `scripts/zoodata.py:2952-2985` **Vulnerability Type**: Incomplete least-privilege enforcement for allowed API operations **Risk Level**: Low ### Vulnerable Code ```python endpoints = [] if args.endpoints: endpoints.extend([ ("categories", {}, "Category tree"), ("markets/search", {"categoryKeyword": "pet", "pageSize": 1}, "Market search"), ("products/search", {"keyword": "test", "pageSize": 1}, "Product search"), ("products/competitors", {"keyword": "test", "pageSize": 1}, "Competitor lookup"), ]) if args.keyword_endpoints: keyword = (args.keyword or "").strip() or "yoga mat" date = args.date or time.strftime("%Y-%m-%d", time.localtime(time.time() - 86400)) keyword_probes = [ ("keywords/detail", {"keyword": keyword, "date": date}, "Keyword snapshot"), ("keywords/market-profile", {"keyword": keyword, "date": date}, "Keyword market profile"), ( "keywords/trend-profile", {"keyword": keyword, "date": date, "windowPeriods": [4], "granularity": "week"}, "Keyword trend profile", ), ("keywords/extends", {"query": keyword, "date": date, "queryType": "phrase", "pageSize": 1}, "Keyword expansion"), ("keywords/search-results", {"keyword": keyword, "date": date, "pageSize": 1}, "Keyword SERP"), ] endpoints.extend(keyword_probes) if args.asin: endpoints.extend([ ("keywords/product-traffic-terms", {"asin": args.asin, "date": date, "pageSize": 1}, "ASIN traffic terms"), ("keywords/competitor-product-keywords", {"asin": args.asin, "date": date, "pageSize": 1}, "ASIN keyword coverage"), ("keywords/product-traffic-terms-overview", {"asin": args.asin, "date": date}, "ASIN traffic overview"), ]) if keyword: endpoints.append(( "keywords/product-traffic-terms-timeline", {"asin": ar ...[truncated 2192 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Restrict this bundle's `check` command to local credential-presence checks by default. 2. If endpoint probes are retained, limit them to the declared endpoints: - `categories` - `markets/search` - `products/search` 3. Add a bundle-specific endpoint allowlist and enforce it inside `api_call()`, not only at CLI subcommand dispatch. 4. Reject any endpoint absent from the bundle manifest before resolving the credential or making a network request. 5. Move broad keyword and competitor diagnostics to a separate reference or administrative Skill with an explicit permission and credit-use boundary. 6. Require clear user confirmation before any multi-endpoint diagnostic operation that consumes credits. 7. Include the exact probe count and estimated credit consumption in the confirmation prompt. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
Findings (16)

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
A second independently detected description-behavior mismatch shows the skill can perform credential checks, product/ASIN detail retrieval, keyword analytics, raw review fetching, and general multi-command research beyond category trend tracking. Hidden or under-declared functionality increases the risk of unauthorized data collection, unexpected external calls, and operator misuse because the published contract does not match the real behavior.

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
A second independently detected description-behavior mismatch shows the skill can perform credential checks, product/ASIN detail retrieval, keyword analytics, raw review fetching, and general multi-command research beyond category trend tracking. Hidden or under-declared functionality increases the risk of unauthorized data collection, unexpected external calls, and operator misuse because the published contract does not match the real behavior.

Description-Behavior Mismatch

High
Confidence
98% confidence
Finding
The shared CLI exposes a much broader command surface than the stated trend-scanner skill purpose, including competitor analysis, listing audit, pricing analysis, review mining, and keyword/SERP tooling. Even with an allowlist mechanism present, bundling oversized multi-purpose tooling into a narrowly scoped skill increases the risk of privilege creep, accidental misuse, or manifest/configuration mistakes that grant capabilities beyond what users and orchestrators expect.

Rp1

Medium
Category
MCP Rug Pull
Confidence
89% confidence
Finding
The README instructs users to install the skill via `npx skills add SerendipityOneInc/ZooData-Skills` without pinning a specific version or immutable reference. This can cause users to fetch and execute whatever package state is current at install time, increasing supply-chain risk if the package is later compromised, replaced, or updated with unsafe code.

Lp3

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding
The skill advertises significant capabilities (environment access, file reads, network, and shell execution) but does not declare explicit tool scope or permissions boundaries in the manifest. That creates a confused-deputy risk: an orchestrator or reviewer cannot reliably constrain what the skill may do, increasing the chance of over-broad execution, secret exposure, or unintended command use.

Vague Triggers

Medium
Confidence
95% confidence
Finding
The trigger phrases include broad everyday language such as general questions about what is growing or where the market is heading, which can cause over-selection of this skill in situations where the user did not intend category-wide scanning. Over-broad invocation increases the chance of unnecessary API calls, unintended persistence, and execution of a more capable tool than needed.

Session Persistence

Medium
Category
Rogue Agent
Content
### Local Interface Failure Output

For a terminal interface failure, respond in the user's language that the trend scan could not be completed, then list succeeded and failed endpoint identifiers and state that existing scan state remains unchanged. Do not emit trend signals, hot-category rankings, alerts, or write watchlists, history, or baselines. Keep control tokens, parameters, and retry logs internal unless diagnostics are requested.

## Input
Confidence
88% confidence
Finding
The skill persists watchlists, baselines, alerts, and historical snapshots across runs, which introduces session persistence and local state retention. Even if intended for monitoring, stored category interests, parameters, and historical outputs can reveal user research strategy or business intent, and persistence can outlive user expectations unless clearly controlled.

External Transmission

Medium
Category
Data Exfiltration
Content
# ─── Configuration ───────────────────────────────────────────────────────────

DEFAULT_BASE_URL = "https://api.zoodata.ai/openapi/v2"
API_BASE_PATH = "/openapi/v2"
KEYWORD_DATE_RANGE_MAX_DAYS = 93
KEYWORD_TIMELINE_MAX_DAYS = 61
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
# ─── Configuration ───────────────────────────────────────────────────────────

DEFAULT_BASE_URL = "https://api.zoodata.ai/openapi/v2"
API_BASE_PATH = "/openapi/v2"
KEYWORD_DATE_RANGE_MAX_DAYS = 93
KEYWORD_TIMELINE_MAX_DAYS = 61
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
# ─── Configuration ───────────────────────────────────────────────────────────

DEFAULT_BASE_URL = "https://api.zoodata.ai/openapi/v2"
API_BASE_PATH = "/openapi/v2"
KEYWORD_DATE_RANGE_MAX_DAYS = 93
KEYWORD_TIMELINE_MAX_DAYS = 61
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
print("    export ZOODATA_API_KEY='hms_live_yourkey'", file=sys.stderr)
    print("", file=sys.stderr)
    print("  Method 2: User-home config (persistent, shared across all skills)", file=sys.stderr)
    print("    mkdir -p ~/.zoodata && chmod 700 ~/.zoodata", file=sys.stderr)
    print('    (umask 077; echo \'{"api_key":"hms_live_yourkey"}\' > ~/.zoodata/config.json)', file=sys.stderr)
    print("    # keep the file private (0600) — it holds a bearer credential", file=sys.stderr)
    print("", file=sys.stderr)
Confidence
80% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Session Persistence

Medium
Category
Rogue Agent
Content
print("    export ZOODATA_API_KEY='hms_live_yourkey'", file=sys.stderr)
    print("", file=sys.stderr)
    print("  Method 2: User-home config (persistent, shared across all skills)", file=sys.stderr)
    print("    mkdir -p ~/.zoodata && chmod 700 ~/.zoodata", file=sys.stderr)
    print('    (umask 077; echo \'{"api_key":"hms_live_yourkey"}\' > ~/.zoodata/config.json)', file=sys.stderr)
    print("    # keep the file private (0600) — it holds a bearer credential", file=sys.stderr)
    print("", file=sys.stderr)
Confidence
76% confidence
Finding
The script supports persistent storage of a bearer API key in ~/.zoodata/config.json and explicitly encourages that setup as an option. While it recommends restrictive permissions, any persistent credential storage expands the blast radius of local compromise and broadens access across all skills using the shared config, which is more sensitive in a narrowly scoped skill context.

Context-Inappropriate Capability

Medium
Confidence
94% confidence
Finding
The review-intelligence and prompt-generation module enables extraction and clustering of raw review content, which is outside the declared category-trend scanning purpose. In a skill that is supposed to analyze category trends over time, embedding review-processing capabilities increases access to potentially sensitive or unexpected data flows and widens what an agent could do if command scoping fails.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
This Python file contains a natural-language prompt literal that instructs the caller's LLM to make all output English and to translate non-English reviews before extraction. That is a language-policy constraint applied unconditionally, with no user choice or locale-specific justification in the file.

Context-Inappropriate Capability

Medium
Confidence
95% confidence
Finding
The keyword traffic, ASIN traffic-term, and SERP analytics commands materially expand the skill from category trend analysis into search-intelligence and product-level monitoring. That mismatch creates unnecessary capability exposure and can lead to over-collection or misuse if the orchestration layer assumes this skill is limited to trend scanning.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Content
"priceMin", "priceMax", "ratingMin", "ratingMax", "bsrMin", "bsrMax",
                 "salesGrowthRateMin", "salesGrowthRateMax", "sellerCountMin", "sellerCountMax",
                 "variantCountMin", "variantCountMax"):
        val = getattr(args, attr.replace("Min", "_min").replace("Max", "_max")
                      .replace("monthly", "monthly_").replace("review", "review_")
                      .replace("sales", "sales_").replace("Growth", "_growth_")
                      .replace("Rate", "rate_").replace("price", "price_")
Confidence
50% confidence
Finding
Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Static analysis

No suspicious patterns detected.