Back to skill

Security audit

amazon-pricing-command-center

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly matches its stated ZooData pricing-analysis purpose, but it needs review because an API key can be sent over plaintext HTTP if the base URL override is set unsafely.

Install only if you trust ZooData and the publisher with submitted ASINs, keywords, category paths, and API-credit usage. Do not set ZOODATA_BASE_URL unless you fully control it; it should remain the default HTTPS ZooData endpoint. Prefer a pinned or verified release instead of the README's floating npx install command.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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 (2)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/zoodata.py:55
Finding
Bearer API Credential May Be Transmitted over Plaintext HTTP<![CDATA[ ## Vulnerability Details **File Location**: `scripts/zoodata.py:55-82` and `scripts/zoodata.py:339-378` **Vulnerability Type**: Insufficient transport security validation **Risk Level**: High ### Vulnerable Code ```python def _host_of(url): try: from urllib.parse import urlparse return (urlparse(url if "://" in url else f"https://{url}").hostname or "").lower() except Exception: return "" 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 accepted URL is subsequently used with the bearer credential: ```python 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_a ...[truncated 2789 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Require HTTPS for all non-loopback destinations: ```python from urllib.parse import urlparse def _is_trusted_base_url(url): try: parsed = urlparse(url) except ValueError: return False host = (parsed.hostname or "").lower() if parsed.username is not None or parsed.password is not None: return False if parsed.fragment or parsed.query: return False if host in ("localhost", "127.0.0.1"): return parsed.scheme == "https" return ( parsed.scheme == "https" and host == "api.zoodata.ai" and parsed.port in (None, 443) ) ``` 2. Restrict production credential transmission to the exact origin `https://api.zoodata.ai:443` rather than every `*.zoodata.ai` hostname. 3. Remove `ZOODATA_BASE_URL` in production builds if endpoint replacement is not required for the declared functionality. 4. If local development endpoints are necessary, require a separate explicit development flag and a separate non-production API key. Do not send production credentials to localhost over HTTP. 5. Disable automatic redirects for authenticated requests or validate every redirect target before following it. Strip the `Authorization` header whenever the scheme, host, or port changes. 6. Reject malformed URLs, embedded user information, unexpected ports, query strings, and fragments. 7. Add tests confirming rejection of: - `http://api.zoodata.ai` - `http://subdomain.zoodata.ai` - `https://api.zoodata.ai:444` - URLs containing username or password components - Cross-origin and HTTPS-to-HTTP redirects 8. Update `SKILL.md` so its declared network policy exactly matches the implemented allowlist. ]]>

T08 · Insecure Dependencies

Warning
Location
README.md:20
Finding
Unpinned Remote Installation Chain Can Deliver Unaudited Skill Content<![CDATA[ ## Vulnerability Details **File Location**: `README.md:20-24` **Vulnerability Type**: Unpinned third-party installer and mutable remote source **Risk Level**: Medium ### Vulnerable Code ```markdown ## Install ```bash npx skills add SerendipityOneInc/ZooData-Skills ``` Select **Amazon Pricing Command Center** when prompted. ``` ### Technical Analysis The documented installation command relies on two mutable upstream components: 1. `npx skills` does not specify an exact npm package version. 2. `SerendipityOneInc/ZooData-Skills` does not identify an immutable release tag or commit. As a result, running the command at a later time may execute a different npm installer and retrieve different Skill content from the content reviewed in this audit. The effective installation payload can change without modification of this artifact. No malicious dependency or active compromise was observed in the audited files. The vulnerability is the lack of version pinning and integrity verification, which creates a supply-chain path through which future or compromised upstream content could replace the reviewed implementation. ### Attack Path 1. An attacker compromises the npm package resolved by `npx skills`, its publication credentials, the remote repository, or an associated upstream dependency. 2. The attacker publishes a modified package version or changes the mutable repository content. 3. A user follows the README and runs the unpinned installation command. 4. `npx` resolves and executes the current package rather than a known audited version. 5. The installer retrieves the current repository content rather than a signed immutable revision. 6. The modified installer or Skill content executes with the permissions available to the user and Agent runtime. ### Impact Assessment The exact impact depends on the permissions of the installation process and the installed Skill. A compromised installer can potentially execute arbitrary code with the invoking user ...[truncated 421 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin the installer package to an exact reviewed version: ```bash npx --yes skills@X.Y.Z add SerendipityOneInc/ZooData-Skills ``` 2. Pin the Skill source to an immutable commit or signed release rather than the repository’s mutable default branch. 3. Publish SHA-256 checksums for release artifacts and document a verification command. 4. Sign releases using a verifiable mechanism such as Sigstore or signed Git tags. 5. Use npm lockfiles and exact dependency versions for the installer and its transitive dependencies. 6. Configure CI to generate reproducible release artifacts and verify that published files match the reviewed source commit. 7. Avoid silently selecting the newest available package. Fail closed when the expected version, signature, or checksum cannot be verified. 8. Document the exact installer version, repository revision, and artifact digest corresponding to each Skill release. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (13)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
A second description-behavior mismatch is present: the skill promises explicit RAISE/HOLD/LOWER recommendations, profit simulation, and batch category-grouped workflows, but those controls are not evidenced in the supplied implementation while broader research and probing features are. This can mislead operators into trusting generated outputs as deterministic business logic and may mask broader API access than intended, creating risk of misuse, overcollection, and unexpected spend.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
A second description-behavior mismatch is present: the skill promises explicit RAISE/HOLD/LOWER recommendations, profit simulation, and batch category-grouped workflows, but those controls are not evidenced in the supplied implementation while broader research and probing features are. This can mislead operators into trusting generated outputs as deterministic business logic and may mask broader API access than intended, creating risk of misuse, overcollection, and unexpected spend.

Rp1

Medium
Category
MCP Rug Pull
Confidence
88% confidence
Finding
The README instructs users to install and run the skill via `npx skills add SerendipityOneInc/ZooData-Skills` without pinning a specific version or immutable package reference. This creates a supply-chain risk: if the referenced package is updated maliciously, compromised, or unexpectedly changed, users may execute unreviewed code during installation.

Lp3

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding
The skill advertises access to environment variables, file reads, network, and shell execution but does not declare explicit tool scope restrictions in the manifest. That increases the attack surface because the agent runtime may grant broader capabilities than users or reviewers expect, enabling unintended access to secrets, local files, or arbitrary command execution if the skill instructions or bundled scripts are abused.

Vague Triggers

Medium
Confidence
92% confidence
Finding
The trigger phrases are broad enough to match ordinary pricing or competitor questions, which can cause the skill to activate in situations where the user did not intend to invoke an external API-backed workflow. In this context, unintended activation matters because the skill can read credentials, make network calls, and consume paid API credits, so over-broad routing increases the chance of unnecessary data disclosure and cost-incurring actions.

Description-Behavior Mismatch

Medium
Confidence
96% confidence
Finding
The manifest describes a focused pricing strategy engine for Amazon sellers that auto-detects category, analyzes pricing landscape, and returns RAISE/HOLD/LOWER guidance for supplied ASINs. This file, however, documents and implements many additional command families such as product opportunity discovery, market-entry analysis, listing audit, review deep-dive, keyword intelligence, and raw review extraction, which materially exceed a narrow pricing-command-center scope.

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.

Context-Inappropriate Capability

Medium
Confidence
94% confidence
Finding
This section adds a full prompt-as-data review-analysis toolkit: fetching raw reviews, rendering tagging/reduction prompts for another LLM, and aggregating review insights. While review sentiment can be tangentially useful, exposing general-purpose review-mining and LLM prompt generation is not a direct or obvious requirement of a skill whose declared purpose is pricing strategy and raise/hold/lower recommendations.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The review-tag prompt explicitly instructs the downstream model that output must be in English and to translate non-English text before extracting. This imposes a fixed language policy in natural-language instructions without offering the user a language choice or documenting a justified locale constraint.

Context-Inappropriate Capability

Medium
Confidence
95% confidence
Finding
The keyword-detail, keyword-market-profile, keyword-trend, keyword-extends, SERP, competitor-keyword, and traffic-term timeline commands provide keyword research and search-visibility analytics. Those are broader Amazon SEO/market-research capabilities, not a direct and necessary implementation detail of generating pricing signals for one or more ASINs as described in the manifest.

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.