Back to skill

Security audit

amazon-shopper

Security checks for vulnerabilities and agentic risk

Overview

This read-only shopping skill is mostly coherent, but it needs Review because it can send shopping data to LLM backends by default and has overbroad optional network and subprocess behavior that is not fully disclosed.

Install only if you are comfortable with product queries and extracted product data being sent to an OpenClaw gateway or a locally configured LLM CLI, potentially using that CLI's existing account. Avoid enabling AMAZON_SHOPPER_ALLOW_WEB_RESEARCH on networks where internal HTTPS services are reachable, and run it with a minimal environment so unrelated secrets are not inherited by child processes.

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

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/research.mjs:194
Finding
Opt-In Web Research Allows Server-Side Request Forgery to Arbitrary HTTPS Destinations<![CDATA[ ## Vulnerability Details **File Location**: `scripts/research.mjs:75-88, 194-202`; `scripts/fetch.mjs:58-62, 78-102` **Vulnerability Type**: Server-Side Request Forgery (SSRF) caused by insufficient destination validation **Risk Level**: Medium ### Vulnerable Code `scripts/research.mjs:75-88` accepts HTTPS links extracted from DuckDuckGo results without validating the destination: ```js async function ddgSearch(query, fetcher) { const url = `https://duckduckgo.com/html/?q=${encodeURIComponent(query)}`; try { const r = await fetcher.get(url); if (r.outcome !== "OK") return []; const links = []; const re = /<a[^>]*class="result__a"[^>]*href="([^"]+)"/g; let m; while ((m = re.exec(r.body)) !== null && links.length < 3) { const u = m[1].startsWith("//duckduckgo.com/l/?") ? decodeURIComponent((m[1].match(/uddg=([^&]+)/) || [])[1] || "") : m[1]; if (u && /^https?:/.test(u)) links.push(u); } return links; } catch { return []; } } ``` `scripts/research.mjs:194-202` sends requests to those untrusted result URLs: ```js const links = await ddgSearch(query, web); for (const link of links.slice(0, 2)) { try { const w = await web.get(link); if (w.outcome !== "OK") { log("web", "blocked", link, w.reason); continue; } r = await extractSpec(w.body.slice(0, 8000), "extract_spec_web"); log("web", r.confidence >= CONFIDENCE_THRESHOLD ? "ok" : "insufficient", link, `conf=${r.confidence}`); if (r.confidence >= CONFIDENCE_THRESHOLD) { return { ...r, spec_status: "ok", spec_source: "web" }; } ``` `scripts/fetch.mjs:58-62` validates only the URL scheme: ```js function hostOf(url) { const u = new URL(url); if (u.protocol !== "https:") throw new UrlNotAllowed(`refusing non-HTTPS URL: ${url}`); return u.hostname.toLowerCase(); } ``` `scripts/fetch.mjs:78-102` follows redirects without applying private-address or host-allowlist checks: ```js async function rawGet(url) ...[truncated 3555 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Apply a shared SSRF guard to every web-research request and every redirect: - Require HTTPS. - Reject embedded credentials and malformed hostnames. - Resolve all A and AAAA records. - Reject the request if any resolved address is loopback, private, link-local, CGNAT, multicast, reserved, or unspecified. 2. Revalidate each redirect target before issuing the next request. Do not treat scheme validation as destination validation. 3. Prefer a narrowly defined host policy: - Allow `duckduckgo.com` only for the search request. - Permit producer domains only after explicit validation or operator approval. - Consider returning external links for separate review instead of fetching arbitrary search results automatically. 4. Mitigate DNS rebinding by using a connection mechanism that binds the HTTP request to the validated address while preserving TLS hostname verification. 5. Reject redirects from a public hostname to an IP literal or non-public destination. 6. Introduce response limits for HTML pages: - Check `Content-Length` when available. - Read the body incrementally. - Cancel the stream after a conservative byte limit. - Enforce connection and total-request timeouts. 7. Add regression tests covering direct and redirected requests to IPv4, IPv6, IPv4-mapped IPv6, loopback, private, link-local, and cloud metadata ranges. 8. Correct the security documentation so it does not claim that redirects are securely revalidated until this control is implemented. ]]>

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
scripts/llm.mjs:50
Finding
Spawned LLM and ImageMagick Processes Inherit Unrelated API Credentials<![CDATA[ ## Vulnerability Details **File Location**: `scripts/llm.mjs:50-53`; `scripts/image-hash.mjs:72-74` **Vulnerability Type**: Excessive subprocess privileges through inherited environment variables **Risk Level**: Medium ### Vulnerable Code `scripts/llm.mjs:50-53` starts an external LLM command without an explicit environment: ```js function runCmd(cmd, args, stdinPayload) { return new Promise((resolve, reject) => { const child = spawn(cmd, args, { stdio: ["pipe", "pipe", "pipe"] }); let out = "", err = ""; ``` `scripts/image-hash.mjs:72-74` similarly starts ImageMagick without restricting its environment: ```js const child = spawn(found.path, args, { stdio: ["pipe", "pipe", "pipe"], }); ``` The binary discovery command also inherits the complete environment: ```js const r = spawnSync("which", [bin], { encoding: "utf8" }); ``` ### Technical Analysis Node.js child processes inherit `process.env` by default when the `env` option is omitted. Consequently, each LLM CLI, ImageMagick process, and binary-discovery process receives all environment variables visible to the shopping process. This may include: - `AMAZON_SHOPPER_APIFY_TOKEN` - `AMAZON_SHOPPER_CREATORS_ACCESS_KEY` - `AMAZON_SHOPPER_CREATORS_SECRET_KEY` - Other unrelated credentials belonging to the host process - Runtime configuration and potentially sensitive service endpoints These credentials are unnecessary for LLM inference and image conversion. Their inheritance violates least privilege and weakens the documented claim that credentials are available only to the code paths that communicate with their designated API endpoints. The bare-name LLM allowlist and fixed ImageMagick names reduce command-selection flexibility, but they do not prevent a compromised executable, malicious PATH entry, altered installation, plugin, or vulnerable child process from reading its inherited environment. ### Attack Path 1. The operator configures Apify, Amazon Creators API, or unrelated ...[truncated 1443 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Supply a minimal explicit `env` object to every `spawn()` and `spawnSync()` call. 2. Remove all shopping API credentials and unrelated secrets from subprocess environments. For example, provide only essential variables such as a trusted `PATH`, locale settings, and a dedicated temporary directory. 3. If an LLM command requires provider-specific credentials, allowlist only the exact variables required by that selected command instead of inheriting the entire parent environment. 4. Give ImageMagick an even narrower environment containing no network or API credentials. 5. Avoid invoking `which`. Resolve binaries using trusted installation paths or a controlled resolver operating on a sanitized `PATH`. 6. Where portable fixed paths are unavailable: - Resolve the executable once. - Require an absolute canonical path under a trusted system directory. - Reject symlinks or files writable by untrusted users. - Verify ownership and permissions before execution. 7. Consider running image decoding in a sandbox with: - No network access. - A dedicated unprivileged user. - Read-only filesystem access except for a private temporary directory. - Existing memory, disk, and time limits. 8. Add tests that place sentinel secrets in the parent environment and verify they are absent from every child process. 9. Update credential-handling documentation to state the actual subprocess boundary and the implemented environment allowlist. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (6)

Description-Behavior Mismatch

Medium
Confidence
89% confidence
Finding
The skill metadata explicitly says it searches amazon.es only, but this module accepts multiple Amazon marketplaces via AMAZON_SHOPPER_APIFY_DOMAIN. That mismatch can cause unauthorized cross-region data transmission, violate user expectations/consent, and broaden the external request surface beyond what the skill declares.

Description-Behavior Mismatch

Medium
Confidence
92% confidence
Finding
The manifest promises the skill searches amazon.es and nothing else, but the code supports multiple locales via REGION_HOST and derives Marketplace from an environment variable. That mismatch can silently expand data collection and external request scope beyond what users and integrators were told, undermining trust, consent, and policy assumptions even if it does not directly enable code execution or account compromise.

Description-Behavior Mismatch

High
Confidence
95% confidence
Finding
The module makes outbound LLM calls by default via `openclaw gateway` and, on failure, via local LLM CLIs, which contradicts the stated behavior that no third-party calls occur when optional API keys are unset. This creates a data-flow and trust-boundary issue: shopping queries and possibly page-derived content can be transmitted to external services without explicit user awareness or consent.

Description-Behavior Mismatch

Medium
Confidence
91% confidence
Finding
The subprocess mode is documented to use local CLI authentication such as `claude --print`, which means the skill can leverage credentials already configured on the host despite claiming it reads no user credentials. Even if the skill does not parse credential files directly, it still causes authenticated external access under the user's identity, expanding privacy and billing risk.

Intent-Code Divergence

Medium
Confidence
88% confidence
Finding
The comments explicitly state subprocess mode uses local Claude authentication, undermining the broader claim that the skill reads no user credentials. In practice this can cause authenticated requests to be made through a locally logged-in CLI, which is materially relevant to user trust, privacy, and possible account charges.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
Requests are sent to an external gateway or external/model-backed CLI without any visible user-facing notice in this module, despite handling shopping prompts that may include user preferences or extracted product data. Silent transmission across trust boundaries is a privacy and transparency problem, especially given the manifest's strong assurances about limited external access.

Static analysis

Detected: suspicious.dangerous_exec, suspicious.dynamic_code_execution, suspicious.exposed_secret_literal

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
scripts/image-hash.mjs:72

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
scripts/llm.mjs:56

Dynamic code execution detected.

Critical
Code
suspicious.dynamic_code_execution
Location
scripts/rank.mjs:4

Dynamic code execution detected.

Critical
Code
suspicious.dynamic_code_execution
Location
tests/package-surface.test.mjs:86

File appears to expose a hardcoded API secret or token.

Critical
Code
suspicious.exposed_secret_literal
Location
SKILL.md:536