Back to skill

Security audit

Shopify Blog SEO Optimizer

Security checks for vulnerabilities and agentic risk

Overview

The skill has a legitimate Shopify SEO purpose, but its approval and network safeguards are not strong enough for a tool that can edit live blog content.

Review before installing on a production store. Use least-privilege Shopify scopes, pin the installer/source to a reviewed version, keep skill-hub.env owner-readable only, and do not execute approved plans unless you independently confirm the exact article ID and body/summary match the reviewed report.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/shopify-blog-seo-admin.mjs:406
Finding

Approved update content is not bound to the reviewed report content

Content
View full analysis
`
  • ${escapeHtml(change.area || "Change")}: ${escapeHtml(change.summary || change)}
  • `).join("") || "
  • No candidate changes were supplied.
  • "; const research = (plan.research || []).map((item) => `
  • ${escapeHtml(item.title || item.publisher || item.url || "Source")} — ${escapeHtml(item.supports || item.note || "")}
  • `).join("") || "
  • No research sources attached.
  • "; const findings = rows.map(([priority, area, problem, recommendation]) => `${priority}${escapeHtml(area)}${escapeHtml(problem)}${escapeHtml(recommendation)}`).join("") || `No deterministic blocking findings.`; ``` ```js if (command === "apply") { const plan = await readJson(args.input || "-"); if (!plan.articleId && !plan.article?.id) throw new Error("APPROVAL_PLAN_INVALID: articleId is required."); const article = {}; const updates = plan.updates || plan.candidate?.updates || {}; for (const key of ["body", "summary"]) if (updates[key] !== undefined) article[key] = key === "body" ? safeArticleHtml(updates[key]) : String(updates[key]); if (!Object.keys(article).length) throw new Error("APPROVAL_PLAN_INVALID: updates.body ...[truncated 2823 chars]
    Remediation
    View remediation

    T09 · Insecure Skill Coding Practices

    Error
    Location
    scripts/lib/public-fetch.mjs:56
    Finding

    Public URL fetching is vulnerable to DNS rebinding and validation-to-connection race conditions

    Content
    View full analysis
    isBlockedIp(address))) { throw new Error(`DNS resolved to a private, local, or invalid destination: ${parsed.hostname}`); } return parsed; } export async function fetchPublic(raw, init = {}, options = {}) { let current = String(raw); const headers = { ...(init.headers || {}) }; for (let redirects = 0; redirects <= MAX_REDIRECTS; redirects += 1) { await assertPublicDestination(current, options); const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), options.timeoutMs || 15000); let response; try { response = await fetch(current, { ...init, headers, redirect: "manual", signal: controller.signal }); } finally { clearTimeout(timeout); } ``` ### Technical Analysis The implementation resolves and validates the hostname in `assertPublicDestination`, but it does not use the validated address for the subsequent network connection. The later `fetch` call performs its own hostname resolution. This creates a time-of-check/time-of-use gap: 1. The validation lookup can return a public address. 2. The hostname passes the private-range check. 3. The independent lookup performed by `fetch` can return a different address. 4. The connection can consequently target a private, loopback, link-local, or cloud metadata address. Redirect destinations are revalidated, which is useful, but each redirect remains subject to the same ind ...[truncated 1605 chars]
    Remediation
    View remediation

    T09 · Insecure Skill Coding Practices

    Warning
    Location
    scripts/shopify-blog-seo-admin.mjs:306
    Finding

    Private environment file is created without restrictive filesystem permissions

    Content
    View full analysis
    Remediation
    View remediation

    T08 · Insecure Dependencies

    Warning
    Location
    SKILL.md:99
    Finding

    Installation instructions execute unpinned mutable supply-chain components

    Content
    View full analysis
    Remediation
    View remediation
    ... ``` 2. Pin the Skill source to an immutable commit SHA or signed release tag. 3. Publish SHA-256 checksums for release artifacts and document verification steps. 4. Prefer signed release archives over installation from a mutable default branch. 5. Use package-lock or equivalent integrity metadata wherever package installation occurs. 6. Document the expected installer publisher, repository, commit, and checksum. 7. Require users to review version changes before granting the installed Skill Shopify credentials. 8. Add automated release provenance, such as signed tags and registry provenance attestations. ]]>
    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
    • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
    • Rogue AgentSelf-Modification, Session Persistence
    Findings (16)

    Self-Modification

    High
    Category
    Rogue Agent
    Confidence
    90% confidence
    Finding

    This skill is explicitly an admin write capability that can modify live Shopify article content after approval. Even with approval gates, a compromised or misused agent could alter storefront content, introduce misleading claims, or damage SEO/brand integrity; the context makes this powerful but partially mitigated because writes are limited to body and summary and require explicit approval.

    Content

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

    md
    # Shopify Blog SEO Optimizer
    
    This is an Admin write skill with preview, content audit, research, and post-write verification.
    
    ## What the merchant experiences
    

    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 · SKILL.md (reported line 77)May include surrounding context.

    md
    6. The merchant reviews the report and explicitly approves or rejects the proposed fields.
    7. Only after approval does the skill call `articleUpdate`; it then reads the Article again and verifies semantic markers, links, and HTML safety.
    
    The merchant should never have to hand-write `audit-plan.json` or an access token. The agent owns the candidate and report data; the deterministic helper validates and renders it.
    
    ## Required references
    

    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 · scripts/shopify-blog-seo-admin.mjs (reported line 351)May include surrounding context.

    js
    6. The merchant reviews the report and explicitly approves or rejects the proposed fields.
    7. Only after approval does the skill call `articleUpdate`; it then reads the Article again and verifies semantic markers, links, and HTML safety.
    
    The merchant should never have to hand-write `audit-plan.json` or an access token. The agent owns the candidate and report data; the deterministic helper validates and renders it.
    
    ## Required references
    

    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.
    

    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/report-schema.md (reported line 70)May include surrounding context.

    }

    text
    
    Never place access tokens, client secrets, session cookies, private API responses, or merchant credentials in any artifact.
    

    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/storefront-preview.md (reported line 28)May include surrounding context.

    md
    - Keep the audit report and preview in the same standalone HTML file.
    - Escape report data and treat fetched storefront content as untrusted data. The generated report must set a restrictive Content Security Policy: no scripts, plugins, forms, frames, or base-URL overrides; inline CSS and required article images/media are the only permitted resources.
    - Do not load third-party scripts, remote tracking, or hidden network calls in the report.
    - Do not expose access tokens, client secrets, private store data, or raw API responses.
    
    The report preview passes article HTML through the deterministic sanitizer before insertion. The CSP is an independent defense-in-depth boundary for the local `file://` report and must remain in place even when the sanitizer changes.
    

    Credential Access

    High
    Category
    Privilege Escalation
    Confidence
    60% confidence
    Finding

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

    Content

    Scanner excerpt · tests/test-shopify-blog-seo-optimizer.mjs (reported line 17)May include surrounding context.

    js
    test("public URL validation rejects unsafe destinations", () => {
      assert.equal(validatePublicUrl("https://example.com/article").protocol, "https:");
      assert.throws(() => validatePublicUrl("file:///etc/passwd"), /Only HTTP and HTTPS/);
      assert.throws(() => validatePublicUrl("https://user:pass@example.com/article"), /Credential-bearing/);
      assert.throws(() => validatePublicUrl("http://127.0.0.1:8080/"), /private|local|invalid/i);
      assert.throws(() => validatePublicUrl("http://[::1]/"), /private|local|invalid/i);
    

    Cloud Metadata Access

    High
    Category
    Server-Side Request Forgery
    Confidence
    90% confidence
    Finding

    Code accesses a cloud instance metadata endpoint (e.g. 169.254.169.254). A single request can return temporary IAM credentials, making this a high-value SSRF target for credential theft.

    Content

    Scanner excerpt · tests/test-shopify-blog-seo-optimizer.mjs (reported line 21)May include surrounding context.

    js
    assert.throws(() => validatePublicUrl("https://user:pass@example.com/article"), /Credential-bearing/);
      assert.throws(() => validatePublicUrl("http://127.0.0.1:8080/"), /private|local|invalid/i);
      assert.throws(() => validatePublicUrl("http://[::1]/"), /private|local|invalid/i);
      assert.equal(isBlockedAddress("169.254.169.254"), true);
      assert.equal(isBlockedAddress("8.8.8.8"), false);
    });
    

    Rp1

    Medium
    Category
    MCP Rug Pull
    Confidence
    70% confidence
    Finding

    npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.

    Content

    No source excerpt is available for this finding.

    Autonomous Decision Making

    Medium
    Category
    Excessive Agency
    Confidence
    75% confidence
    Finding

    Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

    Content

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

    Read references/audit-checklist.md and references/eeat-methodology.md. The agent then creates candidate.json with the original Article identity, candidate body and optional summary, a plain-language changes list, research sources, E-E-A-T findings, and the exact updates bundle. Use references/report-schema.md as the contract.

    Render the combined report without asking the merchant to assemble an intermediate plan:

    text
    node <absolute-path-to-skill>/scripts/shopify-blog-seo-admin.mjs report --audit audit.json --candidate candidate.json --output shopify-blog-seo-report.html
    

    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 65)May include surrounding context.

    js
    const cached = tokenCache.get(key);
      if (cached && Date.now() < cached.expiresAt - TOKEN_SKEW_MS) return cached;
      const { clientId, clientSecret } = requireClientCredentials(env);
      const response = await fetch(`https://${env.SHOPIFY_STORE_DOMAIN}/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 }),
    

    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 164)May include surrounding context.

    js
    const cached = tokenCache.get(key);
      if (cached && Date.now() < cached.expiresAt - TOKEN_SKEW_MS) return cached;
      const { clientId, clientSecret } = requireClientCredentials(env);
      const response = await fetch(`https://${env.SHOPIFY_STORE_DOMAIN}/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 }),
    

    Rp1

    Medium
    Category
    MCP Rug Pull
    Confidence
    96% confidence
    Finding

    The onboarding command tells users to run npx skills add ... without pinning an exact package version. npx resolves and executes remote package code, so an unpinned install can pull a newer or compromised release and execute it immediately on the operator's machine, creating a supply-chain execution risk.

    Content

    No source excerpt is available for this finding.

    Missing User Warnings

    Low
    Category
    Not specified by scanner
    Confidence
    88% confidence
    Finding

    The code invokes the Shopify CLI as a subprocess to execute store operations, which is a safety-relevant action under the audit criteria. Although the behavior is understandable from the implementation, this file does not provide a confirmation prompt, user-facing log, or descriptive comment/docstring warning that an external command will be run.

    Content

    No source excerpt is available for this finding.

    Missing User Warnings

    Low
    Category
    Not specified by scanner
    Confidence
    91% confidence
    Finding

    This function sends GraphQL queries and variables to the Shopify Admin API, which can include user or store content data. In this file there is no confirmation prompt, user-facing log/print, or explanatory comment/docstring disclosing that network transmission occurs.

    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:34

    File appears to expose a hardcoded API secret or token.

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

    File appears to expose a hardcoded API secret or token.

    Critical
    Code
    suspicious.exposed_secret_literal
    Location
    SKILL.md:118