Back to skill

Security audit

Shopify Localization & Markets Auditor

Security checks for vulnerabilities and agentic risk

Overview

This is a coherent Shopify auditing skill, but it asks for broad store-changing permissions and has report/network handling weaknesses that users should review before installing.

Install only if you are comfortable granting Shopify store administration access to the agent. Prefer a read-only Shopify grant for audits, avoid long-running credentials unless needed, do not grant write_translations for this skill, and open generated HTML reports only from trusted audit data until the report serialization issue is fixed.

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

Error
Location
scripts/shopify-markets-localization-auditor.mjs:1688
Finding

Executable Script Injection in Generated HTML Reports

Content
View full analysis
const report = {{REPORT_DATA_JSON}}; const isZh = String(report.lang || "").toLowerCase().startsWith("zh"); ``` The report command accepts its source data from a caller-controlled JSON file: ```javascript async function cmdReport(args) { if (!args.input) throw new Error("--input is required"); if (!args.output) throw new Error("--output is required"); const audit = JSON.parse(await readFile(args.input, "utf8")); audit.reportLang = resolveReportLang(args.lang, audit.reportLang); await renderReport(audit, args.output); console.log(JSON.stringify({ ok: true, output: args.output }, null, 2)); } ``` ### Technical Analysis `JSON.stringify()` produces valid JavaScript data syntax, but it does not make data safe for direct embedding in an HTML ``. A string in the report model containing a payload such as: ```html ``` terminates the original script element before JavaScr ...[truncated 2096 chars]
Remediation
View remediation
{{REPORT_DATA_JSON}} ``` 3. Before embedding JSON in HTML, escape characters that are significant to the HTML parser: ```javascript function serializeForHtml(value) { return JSON.stringify(value) .replace(/&/g, "\\u0026") .replace(//g, "\\u003e") .replace(/\u2028/g, "\\u2028") .replace(/\u2029/g, "\\u2029"); } ``` 4. Use the safe serializer for `{{REPORT_DATA_JSON}}`. 5. Continue applying contextual HTML escaping to every value used in `innerHTML`. 6. Add a restrictive Content Security Policy. Prefer moving report-rendering JavaScript to a static local file so inline scripts can be disabled. 7. Add regression tests using values containing: - `` - HTML tags and event handlers - Ampersands and encoded entities - U+2028 and U+2029 8. Treat both user-supplied audit files and Shopify-provided strings as untrusted input. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/shopify-markets-localization-auditor.mjs:206
Finding

Server-Side Request Forgery Through Storefront Resolution and Redirect Following

Content
View full analysis
= 16 && Number(hostname.split(".")[1]) <= 31) ) { throw new Error(`Access to private address "${hostname}" is blocked.`); } return parsed.href; } catch (err) { throw new Error(`Invalid or unsafe URL "${value}": ${err.message}`); } } ``` The storefront request validates only the initial URL and then follows redirects automatically: ```javascript ...[truncated 3084 chars]
Remediation
View remediation

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
scripts/shopify-markets-localization-auditor.mjs:16
Finding

Read-Only Audit Operations Require Unnecessary Shopify Write Permissions

Content
View full analysis
.myshopify.com --scopes read_locales,write_locales,read_markets,write_markets,read_translations,write_translations,read_shipping,read_legal_policies,read_products --json ``` ### Technical Analysis The Skill supports read-only functions such as: - Connection checks. - Markets and locale audits. - Translation-readiness calculations. - Shipping and legal-policy checks. - Storefront analysis. - Report generation. Those functions do not require write privileges. Nevertheless, the globally defined scope set includes: - `write_locales` - `write_markets` - `write_translations` The Skill documentation explicitly states that translation writes are outside its supported scope. Consequently, `write_translations` is not needed even for approved fixes. The executable does support limited, ...[truncated 1625 chars]
Remediation
View remediation
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
  • System Prompt LeakageDirect Leakage, Indirect Extraction, Tool-Based Exfiltration
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (15)

Direct Prompt Extraction

High
Category
System Prompt Leakage
Confidence
85% confidence
Finding

Skill contains instructions that could directly expose system prompts, internal rules, or hidden instructions to users or external parties.

Content

Scanner excerpt · references/business-research-method.md (reported line 139)May include surrounding context.

md
- family-pet ownership
- safety-led buying behavior

## Output Rules

The business suggestion section must include:

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · references/onboarding-guide.md (reported line 13)May include surrounding context.

md
### Quick connection (recommended)

Use this for a first run or occasional work. The merchant supplies a store address, never an access token or API secret.

1. Ensure Shopify CLI 3.93.0+ is available.
2. Create private `skill-hub.env` in the merchant's working directory with the skill's `init-env` command. Keep it ignored by Git; never place it in the skill folder.

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · references/onboarding-guide.md (reported line 48)May include surrounding context.

md
### Quick connection (recommended)

Use this for a first run or occasional work. The merchant supplies a store address, never an access token or API secret.

1. Ensure Shopify CLI 3.93.0+ is available.
2. Create private `skill-hub.env` in the merchant's working directory with the skill's `init-env` command. Keep it ignored by Git; never place it in the skill folder.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding

The skill clearly expects access to environment variables and external networked systems (Shopify APIs/storefront crawling), but it does not declare an explicit tool/permission scope. That mismatch can cause overbroad runtime access, weak operator review, and accidental execution in contexts that did not clearly consent to env and network use.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The bundled command examples explicitly pass --lang zh-CN and --lang en, which directs report generation in fixed languages. The file does not state here that language selection should be user-chosen or optional, so this can violate a language/locale policy requiring user opt-in.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The rule says --lang auto follows the machine locale, which can cause customer-facing reports to be generated in the analyst's or runtime environment's locale rather than the merchant's intended language. In a commerce audit context, this can expose language or regional inference without explicit user choice and can produce incorrect or privacy-sensitive localization behavior, especially when reports are shared externally.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

This helper obtains a general Shopify Admin API token via client credentials and exposes a generic GraphQL function for arbitrary queries. In a skill whose stated purpose is read-only auditing, that design can exceed least-privilege expectations and makes misuse easier if other parts of the skill request broader scopes or issue mutating GraphQL operations.

Content

No source excerpt is available for this finding.

Dynamic Request Target

Medium
Category
Server-Side Request Forgery
Confidence
60% confidence
Finding

Request target host is built from a dynamic or untrusted value. If the host is attacker-influenced, this enables SSRF to arbitrary internal or metadata endpoints.

Content

Scanner excerpt · scripts/lib/shopify-dev-dashboard-auth.mjs (reported line 8)May include surrounding context.

js
export function isDevDashboardMode(env = {}) { return (env.SKILL_HUB_SHOPIFY_ACCESS_METHOD || "shopify_cli_oauth") === "dev_dashboard_client_credentials"; }
export function normalizeMyShopifyDomain(value) { const raw = String(value || "").trim(); const match = raw.match(/admin\.shopify\.com\/store\/([^/\s?#]+)/i); const host = match ? `${match[1]}.myshopify.com` : raw.replace(/^https?:\/\//i, "").replace(/\/.*$/, ""); if (!/^[a-z0-9][-a-z0-9]*\.myshopify\.com$/i.test(host)) throw new Error("INVALID_STORE_DOMAIN: Provide a Shopify admin URL or your .myshopify.com address."); return host.toLowerCase(); }
function credentials(env) { const clientId = String(env.SKILL_HUB_SHOPIFY_CLIENT_ID || "").trim(); const clientSecret = String(env.SKILL_HUB_SHOPIFY_CLIENT_SECRET || "").trim(); if (!clientId || !clientSecret) throw new Error("DEV_DASHBOARD_CREDENTIALS_REQUIRED: Add the Client ID and Client Secret to your private configuration, or choose quick browser connection instead."); return { clientId, clientSecret }; }
export async function getDevDashboardAccess(env, shopInput) { const shop = normalizeMyShopifyDomain(shopInput || env.SKILL_HUB_SHOPIFY_STORE_DOMAIN); const { clientId, clientSecret } = credentials(env); const key = `${shop}:${clientId}`; const cached = tokenCache.get(key); if (cached && Date.now() < cached.expiresAt - TOKEN_SKEW_MS) return cached; const response = await fetch(`https://${shop}/admin/oauth/access_token`, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams({ grant_type: "client_credentials", client_id: clientId, client_secret: clientSecret }) }); const payload = await response.json().catch(() => ({})); if (!response.ok || !payload.access_token || !payload.expires_in) { const detail = String(payload.error_description || payload.error || ""); const code = /shop_not_permitted/i.test(detail) ? "DEV_DASHBOARD_STORE_NOT_PERMITTED" : (response.status === 401 || response.status === 403 ? "DEV_DASHBO
...[truncated 26 chars]

Dynamic Request Target

Medium
Category
Server-Side Request Forgery
Confidence
60% confidence
Finding

Request target host is built from a dynamic or untrusted value. If the host is attacker-influenced, this enables SSRF to arbitrary internal or metadata endpoints.

Content

Scanner excerpt · scripts/lib/shopify-dev-dashboard-auth.mjs (reported line 10)May include surrounding context.

js
export function isDevDashboardMode(env = {}) { return (env.SKILL_HUB_SHOPIFY_ACCESS_METHOD || "shopify_cli_oauth") === "dev_dashboard_client_credentials"; }
export function normalizeMyShopifyDomain(value) { const raw = String(value || "").trim(); const match = raw.match(/admin\.shopify\.com\/store\/([^/\s?#]+)/i); const host = match ? `${match[1]}.myshopify.com` : raw.replace(/^https?:\/\//i, "").replace(/\/.*$/, ""); if (!/^[a-z0-9][-a-z0-9]*\.myshopify\.com$/i.test(host)) throw new Error("INVALID_STORE_DOMAIN: Provide a Shopify admin URL or your .myshopify.com address."); return host.toLowerCase(); }
function credentials(env) { const clientId = String(env.SKILL_HUB_SHOPIFY_CLIENT_ID || "").trim(); const clientSecret = String(env.SKILL_HUB_SHOPIFY_CLIENT_SECRET || "").trim(); if (!clientId || !clientSecret) throw new Error("DEV_DASHBOARD_CREDENTIALS_REQUIRED: Add the Client ID and Client Secret to your private configuration, or choose quick browser connection instead."); return { clientId, clientSecret }; }
export async function getDevDashboardAccess(env, shopInput) { const shop = normalizeMyShopifyDomain(shopInput || env.SKILL_HUB_SHOPIFY_STORE_DOMAIN); const { clientId, clientSecret } = credentials(env); const key = `${shop}:${clientId}`; const cached = tokenCache.get(key); if (cached && Date.now() < cached.expiresAt - TOKEN_SKEW_MS) return cached; const response = await fetch(`https://${shop}/admin/oauth/access_token`, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams({ grant_type: "client_credentials", client_id: clientId, client_secret: clientSecret }) }); const payload = await response.json().catch(() => ({})); if (!response.ok || !payload.access_token || !payload.expires_in) { const detail = String(payload.error_description || payload.error || ""); const code = /shop_not_permitted/i.test(detail) ? "DEV_DASHBOARD_STORE_NOT_PERMITTED" : (response.status === 401 || response.status === 403 ? "DEV_DASHBO
...[truncated 26 chars]

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The manifest frames the skill primarily as an auditor that reviews Markets, languages, shipping, storefront localization, and SEO basics, with fixes tied to that audit workflow. In code, the required scopes and apply path include direct write operations that change shop locales and market currency settings, which is broader than a purely audit/report behavior and materially changes store configuration.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
87% confidence
Finding

The CLI defaults --lang to auto, and the language resolution logic later chooses zh-CN or en from the host system locale rather than asking the user to choose. This can force a language/locale choice implicitly, which conflicts with the policy against locale enforcement without user opt-in.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The code resolves store domains by fetching arbitrary public URLs and also crawls the storefront homepage and linked policy URLs. Although it blocks common private IP ranges, it still performs outbound network access beyond Shopify Admin auditing, which can be abused for unintended external requests, data exfiltration via request side effects, or expansion of the tool's trust boundary.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

resolveReportLang maps auto to only zh-CN or en using Intl.DateTimeFormat().resolvedOptions().locale, which imposes a language choice derived from environment settings rather than explicit user preference. The code does not offer an interactive opt-in or broader language choice at that decision point.

Content

No source excerpt is available for this finding.

Scope Creep

Low
Category
Excessive Agency
Confidence
75% confidence
Finding

Skill's behavior or capabilities extend beyond its stated purpose. Scope creep allows an agent to perform actions unrelated to its documented functionality, increasing the attack surface.

Content

Scanner excerpt · SKILL.md (reported line 99)May include surrounding context.

md
- Recommend `shopify_cli_oauth` for a quick browser connection.
- Use `dev_dashboard_client_credentials` only when the merchant requests a trusted long-running connection for their own store.
- During Dev Dashboard onboarding, ask whether unattended future permission releases are desired; if yes, configure the optional Automation Token privately. Follow the two-consent upgrade flow in `references/onboarding-guide.md`; never silently broaden scopes.

## Scope

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
88% confidence
Finding

The script sets the page locale to "zh-CN" only for Chinese reports and otherwise forces English content/UI labels. This is a natural-language locale policy concern because users of other locales are not given an opt-in or choice; the template falls back to English by default.

Content

No source excerpt is available for this finding.

Static analysis

Detected: suspicious.env_credential_access, suspicious.exposed_secret_literal

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
scripts/lib/shopify-dev-dashboard-auth.mjs:4

File appears to expose a hardcoded API secret or token.

Critical
Code
suspicious.exposed_secret_literal
Location
references/onboarding-guide.md:45