Back to skill

Security audit

amazon-review-intelligence-extractor

Security checks for vulnerabilities and agentic risk

Overview

The skill is mostly a disclosed ZooData review-analysis integration, but it handles an API bearer key and untrusted review text in ways that deserve review before installation.

Review this carefully before installing if you will use a real ZooData API key. Prefer setting the key only in the environment, use a scoped/low-credit key if possible, avoid unpinned install commands, and treat fallback results based on raw reviews as potentially influenced by untrusted review text.

Vulnerability Patterns
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • 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)

T01 · Skill Instruction Hijacking

Error
Location
scripts/zoodata.py:910
Finding
Untrusted Amazon Review Content Is Embedded Directly into Agent Instructions<![CDATA[ ## Vulnerability Details **File Location**: `scripts/zoodata.py:910-951` **Related Workflow**: `SKILL.md:197-216` **Vulnerability Type**: Prompt injection through untrusted review content **Risk Level**: High ### Vulnerable Code ```python def render_review_map_prompt(review: dict, product_title: str = "", product_category: str = "") -> str: title = review.get("title") or "" body = review.get("body") or "" full = f"{title}. {body}" if title else body text = full[:500] rating = review.get("rating") or 3 verified = bool(review.get("verifiedPurchase")) return f"""IMPORTANT: Respond ONLY with a JSON object matching the schema below. Output must be in English — translate non-English text before extracting. You are an expert data extraction specialist analyzing product reviews. Extract only what is EXPLICITLY mentioned — do not infer. JSON schema: {{ "sentiment": "positive" | "neutral" | "negative", "mentioned_scenarios": [string], "mentioned_issues": [string], "mentioned_positives": [string], "mentioned_improvements": [string], "mentioned_buying_factors": [string], "mentioned_pain_points": [string], "user_profiles": [string], "mentioned_usage_times": [string], "mentioned_usage_locations": [string], "mentioned_behaviors": [string], "keywords": [string] }} Rules: - sentiment: positive (4-5 stars or praise), neutral (3 stars / mixed), negative (1-2 stars or complaint) - pain_points = problems EXPERIENCED AFTER USE. NOT problems the product solves. - issues vs pain_points: issues = product defects; pain_points = UX frustrations - user_profiles: include ONLY if the reviewer explicitly states an identity - consistent naming across reviews - use empty arrays [] for categories with no mentions, never null INPUT: Product Category: {product_category or '(unknown)'} Product Title: {product_title or '(unknown)'} Review Rating: {rating}/5 stars Verified Purchase: {'Yes' if verified else 'No'} Review Text: \"\"\ ...[truncated 2428 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Treat every API-provided review field as untrusted data. 2. Pass review content through a structured model input or tool-result channel instead of concatenating it into an instruction string. 3. Serialize review data as JSON and clearly identify it as inert data: ```python review_payload = json.dumps({ "title": title, "body": body, "rating": rating, "verifiedPurchase": verified, }, ensure_ascii=False) ``` 4. Add explicit model instructions stating that commands, policies, schemas, or role changes found inside review data must never be followed. 5. Avoid delimiter schemes that untrusted text can terminate. If textual delimiters remain necessary, encode or escape delimiter sequences before interpolation. 6. Validate generated output against `REVIEW_MAP_SCHEMA`, reject unknown fields, enforce array and string limits, and verify that extracted phrases are supported by the source review. 7. Apply analogous protections to candidate phrases passed into `render_review_reduce_prompt()`, because those phrases originate from earlier processing of untrusted reviews. 8. Add adversarial tests covering embedded triple quotes, fake system messages, requests to ignore prior instructions, and malformed JSON payloads. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/zoodata.py:330
Finding
Bearer Credential Is Not Protected Across Automatic HTTP Redirects<![CDATA[ ## Vulnerability Details **File Location**: `scripts/zoodata.py:330-378` **Vulnerability Type**: Credential disclosure through insufficient redirect validation **Risk Level**: High ### Vulnerable Code ```python def api_call(endpoint: str, params: dict) -> dict: 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() params = {k: v for k, v in params.items() if v is not None} 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]) 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)", } 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") with urllib.request.urlopen(req, timeout=REQUEST_TIMEOUT) as resp: transport_status = getattr(resp, "status", None) if not isinstance(transport_status, int): transport_status = resp.getcode() response_body = resp.read() ``` ### Technical Analysis The implementation checks whether the initial `BASE_URL` belongs to a trusted hostname before constructing a re ...[truncated 1939 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Disable automatic redirects for authenticated requests, or install a custom `urllib.request.HTTPRedirectHandler`. 2. Validate every redirect destination using a strict origin allowlist before following it. 3. Require HTTPS for all credential-bearing production requests. 4. Remove `Authorization` whenever the scheme, hostname, or port changes. 5. Prefer an exact production origin such as `https://api.zoodata.ai` rather than trusting every `.zoodata.ai` subdomain. 6. Make localhost support an explicit development-only mode and use a separate test credential. 7. Reject HTTPS-to-HTTP downgrade redirects. 8. Limit redirect depth and detect loops. 9. Add automated tests for: - Same-origin redirects - Cross-origin redirects - HTTPS-to-HTTP redirects - Redirects to localhost - Redirects to lookalike domains - Verification that `Authorization` is absent from rejected or cross-origin requests ]]>

T08 · Insecure Dependencies

Warning
Location
README.md:38
Finding
Installation Instructions Execute Unpinned Supply-Chain Components<![CDATA[ ## Vulnerability Details **File Location**: `README.md:38-42` **Vulnerability Type**: Unpinned package and repository installation **Risk Level**: Medium ### Vulnerable Code ```markdown ## Install ```bash npx skills add SerendipityOneInc/ZooData-Skills ``` Select **Amazon Review Intelligence Extractor** when prompted. ``` ### Technical Analysis The installation command invokes `skills` through `npx` without specifying an exact package version or integrity value. It also identifies the Skill repository without pinning an immutable commit or signed release. As a result, the code retrieved or executed at installation time can differ from the artifact reviewed during this audit. Changes to the npm package, dependency graph, installer behavior, repository default branch, or release assets can modify the effective payload after review. This is a supply-chain integrity weakness rather than evidence that the currently inspected files contain a malicious dependency. ### Attack Path 1. A user follows the documented installation command. 2. `npx` resolves the currently published version of the `skills` package rather than an audited fixed version. 3. The installer resolves the current state of `SerendipityOneInc/ZooData-Skills`. 4. An npm account takeover, malicious package update, compromised repository, or unauthorized branch modification changes the retrieved content. 5. The mutable installer or repository content is executed or installed with the invoking user’s permissions. 6. The installed Skill can therefore contain code not represented by this audit. ### Impact Assessment A compromised installation chain could execute arbitrary code with the permissions of the user running `npx`, replace Skill files, harvest environment variables, or install additional malicious components. The current repository does not demonstrate such compromise. The risk arises because users are directed to trust mutable upstream components without version, commit, sign ...[truncated 37 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin the `skills` package to an audited exact version: ```bash npx --yes skills@X.Y.Z add SerendipityOneInc/ZooData-Skills ``` 2. Pin the Skill source to an immutable commit hash or signed release tag. 3. Publish SHA-256 hashes for release artifacts and require verification before installation. 4. Sign releases and document signature-verification procedures. 5. Commit a lockfile for any installation tooling that has dependencies. 6. Avoid lifecycle scripts or automatic execution of downloaded code where possible. 7. Document the exact package version and repository commit covered by each security audit. 8. Re-audit and publish new integrity metadata whenever either the installer or Skill source changes. ]]>
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
98% confidence
Finding
The skill claims to use all 11 review endpoints for review intelligence, but the specification shows use of a broader CLI and a single review endpoint with client-side splitting plus local prompt/aggregation utilities. This creates deceptive capability signaling and can mislead operators about what data is collected, how results are produced, and what actions the skill may perform.

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding
The skill claims to use all 11 review endpoints for review intelligence, but the specification shows use of a broader CLI and a single review endpoint with client-side splitting plus local prompt/aggregation utilities. This creates deceptive capability signaling and can mislead operators about what data is collected, how results are produced, and what actions the skill may perform.

Description-Behavior Mismatch

High
Confidence
98% confidence
Finding
The skill is described as a review-intelligence extractor, but the bundled CLI exposes a much broader Amazon market-research surface including market-entry, competitor-analysis, pricing-analysis, listing-audit, opportunity-scan, keyword intelligence, and broad product discovery. This scope mismatch can enable unauthorized data access/workflows beyond user expectations and policy boundaries, especially if the allowlist manifest is absent, causing the code to default to full-surface exposure.

Rp1

Medium
Category
MCP Rug Pull
Confidence
90% confidence
Finding
The README instructs users to install the skill via `npx skills add SerendipityOneInc/ZooData-Skills` without pinning a specific version or immutable commit. This creates a supply-chain risk: users may fetch whatever package/version is current at install time, so a compromised publisher account, malicious update, or unexpected breaking change could deliver unreviewed code.

Lp3

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding
The skill exposes meaningful capabilities—environment access, file reads, network access, and shell execution—without an explicit tool-scope declaration. That makes the trust boundary unclear and can lead to overbroad execution in hosts that rely on manifest-declared permissions, increasing the risk of unintended command execution or data access.

Vague Triggers

Medium
Confidence
93% confidence
Finding
The trigger phrases are broad enough to match many generic product-analysis requests, which can cause the skill to activate in contexts the user did not specifically intend. In a skill with shell, file, network, and paid API usage, overbroad invocation increases the chance of unnecessary data access, command execution, or credit consumption.

Description-Behavior Mismatch

Medium
Confidence
95% confidence
Finding
The manifest presents the skill as using pre-analyzed ZooData review insights, but the fallback explicitly fetches live raw Amazon reviews and has the model analyze them. This changes the data provenance and privacy/compliance posture, and may expose users to undisclosed collection and processing of raw third-party content.

Intent-Code Divergence

Medium
Confidence
93% confidence
Finding
The documentation says the skill should only report what the API returns, yet the fallback requires the model to generate its own tags and semantic clusters. That can cause fabricated or non-reproducible outputs to be presented with the same authority as API-derived data, undermining integrity and potentially misleading downstream decisions.

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
60% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Natural-Language Policy Violations

Medium
Confidence
97% confidence
Finding
This code embeds a natural-language instruction requiring that output 'must be in English' and to translate non-English text before extraction. That is a locale/language policy constraint applied unconditionally, with no user choice or documented region-specific justification.

Context-Inappropriate Capability

Medium
Confidence
95% confidence
Finding
The code exposes keyword SERP, traffic-term, and competitor keyword intelligence endpoints that are not justified by the stated review-intelligence purpose. In a skill marketed for consumer review insights, these capabilities materially expand data collection and competitive intelligence functionality, increasing the chance of misuse, over-collection, or bypass of product-level governance expectations.

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.