Back to skill

Security audit

Shopify Bulk Upload

Security checks for vulnerabilities and agentic risk

Overview

The skill is a real Shopify bulk uploader, but it can change live store data at scale and has avoidable token and network-safety risks users should review before installing.

Review carefully before installing or running. Use a development Shopify store first, back up catalog data, use the smallest possible API scopes, keep the token out of command-line history when possible, verify the store URL is exactly your HTTPS myshopify.com host, and only process trusted product files and image URLs. Consider pinning dependencies and adding an explicit confirmation prompt before production uploads.

Vulnerability Patterns
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (2)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/shopify_bulk_upload.py:104
Finding
Unrestricted Image URLs Enable Blind Server-Side Request Forgery<![CDATA[ ## Vulnerability Details **File Location**: `scripts/shopify_bulk_upload.py:104-106`, with attacker-controlled input reaching the vulnerable method at `scripts/shopify_bulk_upload.py:200-205` **Vulnerability Type**: Blind server-side request forgery (SSRF) **Risk Level**: Medium ### Vulnerable Code ```python def upload_image(self, product_id, image_url): """上传产品图片""" try: # 下载图片 img_response = requests.get(image_url, timeout=CONFIG["image_timeout"]) if img_response.status_code != 200: logger.warning(f"无法下载图片: {image_url}") return None ``` The image URL originates from product-file input: ```python # 上传图片 images = product_data.get("images", "") if images: image_urls = [img.strip() for img in images.split(",")] for img_url in image_urls: if img_url: self.upload_image(product_id, img_url) ``` ### Technical Analysis The `images` field is read from an operator-supplied CSV or Excel file and passed directly to `requests.get()`. The code does not validate: - URL scheme - Destination hostname - Resolved IP address - Redirect targets - Destination port - Loopback, private, link-local, or reserved address ranges - Cloud instance metadata endpoints Consequently, a malicious product file can cause the uploader host to issue HTTP requests to network resources that are not accessible to the attacker directly. The local image download is also unnecessary for the declared workflow. The downloaded bytes are not included in the Shopify request; the method later submits only the original URL as Shopify's `image.src`. This unnecessary request exceeds the minimum network access required for Shopify image import. The current implementation does not return the downloaded response body to the input provider, so the issue is blind SSRF rather than direct response exfiltration. Nevertheless, timing, status-dependent behavior, and application logs may reveal whether a destination is re ...[truncated 1466 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. **Remove the local download entirely.** Shopify is already instructed to retrieve the image through the submitted `src` URL, so the local `requests.get()` call and unused byte encoding should be deleted. 2. If local validation is a functional requirement, implement a strict URL policy: - Permit only `https` URLs. - Reject embedded credentials, fragments, malformed hosts, and unexpected ports. - Resolve the hostname before connecting. - Reject loopback, private, link-local, multicast, unspecified, reserved, and metadata IP ranges for both IPv4 and IPv6. - Revalidate every redirect destination or disable redirects. - Protect against DNS rebinding by connecting only to the validated resolved address. - Apply a maximum response size and validate the response content type. - Use short connection and read timeouts. 3. Consider an explicit domain allowlist if product images are expected to come from known content-delivery or supplier domains. 4. Log rejected destinations without downloading them, and avoid placing sensitive URL components in logs. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/shopify_bulk_upload.py:47
Finding
Unvalidated Shopify Store URL Can Disclose the Admin API Access Token<![CDATA[ ## Vulnerability Details **File Location**: `scripts/shopify_bulk_upload.py:47-69`; unrestricted CLI input is accepted at `scripts/shopify_bulk_upload.py:289-290` **Vulnerability Type**: Credential disclosure through an unvalidated authenticated API destination **Risk Level**: Medium ### Vulnerable Code ```python def __init__(self, store_url=None, access_token=None): self.store_url = store_url or os.getenv("SHOPIFY_STORE_URL") self.access_token = access_token or os.getenv("SHOPIFY_ACCESS_TOKEN") self.api_version = os.getenv("SHOPIFY_API_VERSION", "2024-01") self.session = requests.Session() self.session.headers.update({ "X-Shopify-Access-Token": self.access_token, "Content-Type": "application/json", }) self.created_products = [] self.failed_products = [] def _make_request(self, method, endpoint, data=None, params=None): """发送 API 请求""" url = f"{self.store_url}/admin/api/{self.api_version}/{endpoint}" for attempt in range(CONFIG["retry_count"]): try: if method.upper() == "GET": response = self.session.get(url, params=params, timeout=30) elif method.upper() == "POST": response = self.session.post(url, json=data, timeout=60) elif method.upper() == "PUT": response = self.session.put(url, json=data, timeout=60) elif method.upper() == "DELETE": response = self.session.delete(url, timeout=30) ``` The destination may be supplied without validation through command-line arguments: ```python parser.add_argument("-s", "--store", help="Shopify 店铺 URL") parser.add_argument("-t", "--token", help="Shopify Access Token") ``` ### Technical Analysis The uploader places the Shopify access token in the persistent headers of a `requests.Session`. It then constructs authenticated request URLs by concatenating an unrestricted `store_url` value with the Shopify API path. The store U ...[truncated 2077 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Parse the configured store URL with a standard URL parser before creating or using the authenticated session. 2. Enforce all of the following: - Require the `https` scheme. - Require an exact, operator-approved store hostname. - Normally require a valid `*.myshopify.com` hostname unless a documented Shopify-supported alternative is needed. - Reject embedded credentials, query strings, fragments, and unexpected ports. - Normalize the hostname before comparison to prevent suffix and case-confusion errors. 3. Ask the operator to confirm the normalized destination hostname before the first authenticated request. 4. Disable redirects for authenticated API calls unless every redirect destination is independently validated against the same exact-host policy. 5. Avoid placing the token in global session headers where unrelated requests could inherit it. Add the authentication header only to validated Shopify API requests. 6. Never accept the token through command-line arguments when avoidable, because command lines may be exposed through shell history or process listings. Prefer a protected environment variable or secrets manager. 7. Use the smallest possible Shopify API scopes. Remove `write_inventory` or other scopes if the executed workflow does not require them. 8. Rotate the access token immediately if it may have been sent to an untrusted destination. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
Findings (17)

Credential Access

High
Category
Privilege Escalation
Content
SHOPIFY_API_VERSION=2024-01
```

To get Access Token:
1. Login to Shopify Admin
2. Go to Settings → Apps and sales channels → Develop apps
3. Create App → Configure Admin API scopes
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
SHOPIFY_API_VERSION=2024-01
```

To get Access Token:
1. Login to Shopify Admin
2. Go to Settings → Apps and sales channels → Develop apps
3. Create App → Configure Admin API scopes
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
SHOPIFY_API_VERSION=2024-01
```

To get Access Token:
1. Login to Shopify Admin
2. Go to Settings → Apps and sales channels → Develop apps
3. Create App → Configure Admin API scopes
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The skill explicitly performs bulk product and inventory writes to a Shopify store, but the description does not warn users that it can modify live store data at scale. In this context, omission of a clear warning increases the risk of accidental destructive or unintended changes to products, pricing, and inventory in production.

Missing User Warnings

Medium
Confidence
82% confidence
Finding
This markdown file documents create, update, and delete product endpoints, plus inventory-setting operations, but provides no user warning that these actions modify or remove live store data. Under the markdown-file criteria, descriptions that enable changes affecting user data or system integrity should disclose those risks.

Missing User Warnings

Medium
Confidence
87% confidence
Finding
This code performs safety-relevant operations by creating products through the Shopify API and writing upload results to local JSON files. While progress is logged, there is no clear user-facing warning or confirmation that running the script will change store data and create output artifacts on disk.

Cloud Storage Exfiltration

Medium
Category
Data Exfiltration
Content
# 创建上传器并执行
    uploader = ShopifyUploader(args.store, args.token)
    uploader.upload_from_file(file_path)


if __name__ == "__main__":
Confidence
55% confidence
Finding
Data is uploaded to cloud storage (S3 / GCS / Azure Blob). This may be a legitimate backup or exfiltration to an external bucket. Manual review is recommended.

Natural-Language Policy Violations

Low
Confidence
89% confidence
Finding
This CSV template includes example product titles and descriptions only in Chinese, which can amount to a fixed language choice in user-facing template content. The file does not indicate that Chinese is optional, configurable, or required for a region-specific purpose.

Unpinned Dependencies

Low
Category
Supply Chain
Content
pandas>=2.0.0
requests>=2.28.0
python-dotenv>=1.0.0
openpyxl>=3.1.0
Confidence
94% confidence
Finding
The dependency is specified with a lower bound only, which allows installs to float to different future versions and undermines reproducible builds. This increases supply-chain risk because a later vulnerable or malicious release could be resolved without any manifest change.

Unverifiable Dependency: pandas has 1 known advisory(ies) (CVE-2020-13091 (** DISPUTED ** pandas through 1.0.3 can unserialize and execute commands from an)), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
84% confidence
Finding
Because pandas is not pinned, it is impossible to verify from this manifest whether the installed version avoids known advisories. Even though the cited CVE is disputed and may not affect current versions, the lack of version certainty is itself a supply-chain weakness.

Unpinned Dependencies

Low
Category
Supply Chain
Content
pandas>=2.0.0
requests>=2.28.0
python-dotenv>=1.0.0
openpyxl>=3.1.0
Confidence
94% confidence
Finding
The requests package is unpinned, so dependency resolution may select different versions across environments or over time. That weakens build integrity and can expose deployments to newly introduced vulnerable releases or breaking changes.

Unverifiable Dependency: requests has 16 known advisory(ies) (CVE-2014-1830 (Exposure of Sensitive Information to an Unauthorized Actor in Requests); CVE-2024-47081 (Requests vulnerable to .netrc credentials leak via malicious URLs); CVE-2024-35195 (Requests `Session` object does not verify requests after making first request wi) +13 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
90% confidence
Finding
Requests has multiple historical advisories, and the manifest does not identify which version will actually be installed. Since this library commonly handles outbound HTTP and credentials, version ambiguity can increase the chance of deploying a release with known security flaws.

Unpinned Dependencies

Low
Category
Supply Chain
Content
pandas>=2.0.0
requests>=2.28.0
python-dotenv>=1.0.0
openpyxl>=3.1.0
Confidence
93% confidence
Finding
Using python-dotenv with only a minimum version permits uncontrolled upgrades to later releases. This creates avoidable supply-chain uncertainty and may pull in a version affected by newly disclosed issues.

Unverifiable Dependency: python-dotenv has 2 known advisory(ies) (CVE-2026-28684 (python-dotenv: Symlink following in set_key allows arbitrary file overwrite via ); CVE-2026-28684 (python-dotenv reads key-value pairs from a .env file and can set them as environ)), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
83% confidence
Finding
The manifest does not pin python-dotenv, so it cannot be confirmed whether deployed environments use a version affected by known issues. While impact depends on how the package is used, the uncertainty is avoidable and weakens dependency hygiene.

Unpinned Dependencies

Low
Category
Supply Chain
Content
pandas>=2.0.0
requests>=2.28.0
python-dotenv>=1.0.0
openpyxl>=3.1.0
Confidence
93% confidence
Finding
The openpyxl dependency is not pinned to an exact version, so builds are not reproducible and future installs may silently change behavior or security posture. In a package used to process spreadsheet files, drift can be especially undesirable if parser-related flaws are later introduced.

Unverifiable Dependency: openpyxl has 2 known advisory(ies) (CVE-2017-5992 (Improper Restriction of XML External Entity Reference in Openpyxl); CVE-2017-5992 (Openpyxl 2.4.1 resolves external entities by default, which allows remote attack)), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
88% confidence
Finding
Openpyxl has had XML-related security advisories, and the unpinned requirement makes it unclear whether a safe version will be installed. In contexts where spreadsheets may come from external or untrusted sources, this uncertainty is more dangerous because parser flaws can be reachable.

Natural-Language Policy Violations

Low
Confidence
95% confidence
Finding
Natural-language text in the module docstring and subsequent user-facing messages is presented only in Chinese, which imposes a specific language choice on users. There is no indication that the tool is region-specific or that users can opt into this locale.