Back to skill

Security audit

Whop Digital Sales

Security checks for vulnerabilities and agentic risk

Overview

The skill does what it says by creating Whop products and checkout links, but it can make live public changes to a commercial account with a powerful API key without a dry run or clear safety warning.

Review this carefully before installing or running it. Use a least-privileged or test Whop API key if available, confirm the target company account, and expect the script to create public products, paid plans, and checkout links in that account. Do not run it with a production Company API key unless those exact products and prices are intended.

Vulnerability Patterns
  • 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
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (16)

Tainted flow: 'HEADERS' from os.environ.get (line 11, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/create_whop_products.py (reported line 47)May include surrounding context.

python
"visibility": "public",
    }
    
    resp = requests.post(f"{BASE_URL}/products", headers=HEADERS, json=payload, timeout=15)
    if resp.status_code not in (200, 201):
        print(f"  ❌ {p['name']}: {resp.text[:200]}")
        return None

Tainted flow: 'HEADERS' from os.environ.get (line 11, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/create_whop_products.py (reported line 65)May include surrounding context.

python
"price": p["price"],
            "visibility": "public",
        }
        plan_resp = requests.post(f"{BASE_URL}/v2/plans", headers=HEADERS, json=plan_payload, timeout=15)
        if plan_resp.status_code in (200, 201):
            plan = plan_resp.json().get("data", {}) or plan_resp.json()
            plan_id = plan.get("id", "")

Tainted flow: 'HEADERS' from os.environ.get (line 11, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/create_whop_products.py (reported line 76)May include surrounding context.

python
"plan_id": plan_id,
                "allow_multiple_quantity": False,
            }
            checkout_resp = requests.post(f"{BASE_URL}/checkouts", headers=HEADERS, json=checkout_payload, timeout=15)
            if checkout_resp.status_code in (200, 201):
                checkout = checkout_resp.json().get("data", {}) or checkout_resp.json()
                checkout_url = checkout.get("url", checkout.get("checkout_url", ""))

Tainted flow: 'HEADERS' from os.environ.get (line 11, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/create_whop_products.py (reported line 87)May include surrounding context.

python
"plan_id": plan_id,
                "allow_multiple_quantity": False,
            }
            checkout_resp = requests.post(f"{BASE_URL}/checkouts", headers=HEADERS, json=checkout_payload, timeout=15)
            if checkout_resp.status_code in (200, 201):
                checkout = checkout_resp.json().get("data", {}) or checkout_resp.json()
                checkout_url = checkout.get("url", checkout.get("checkout_url", ""))

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding

The skill documents use of environment secrets and outbound network access to a live commercial API, but it does not declare any explicit tool scope or permissions boundaries. That increases the chance of overbroad execution in environments that support tool gating, making accidental secret access or unintended API calls more likely.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The skill states it will auto-manage products and checkout links and instructs users to run creation scripts, but it does not warn that these actions will make real, irreversible changes in a production Whop account. Users may execute it expecting a dry run and unintentionally create products, plans, or public checkout flows.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The instructions tell users to create and store a Company API key but do not explain that this credential may grant significant control over the connected Whop company account. Without a clear warning, users may expose or misuse a powerful secret, enabling unauthorized product creation, pricing changes, or payment-flow abuse if the key is compromised.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

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

md
import os, json, requests

WHOP_API_KEY = os.environ.get("WHOP_API_KEY", "")
BASE_URL = "https://api.whop.com/api/v1"
HEADERS = {"Authorization": f"Bearer {WHOP_API_KEY}", "Content-Type": "application/json"}

PRODUCTS = [

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/create_whop_products.py (reported line 10)May include surrounding context.

python
import os, json, requests

WHOP_API_KEY = os.environ.get("WHOP_API_KEY", "")
BASE_URL = "https://api.whop.com/api/v1"
HEADERS = {"Authorization": f"Bearer {WHOP_API_KEY}", "Content-Type": "application/json"}

PRODUCTS = [

External Transmission

Medium
Category
Data Exfiltration
Confidence
80% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/create_whop_products.py (reported line 47)May include surrounding context.

python
"visibility": "public",
    }
    
    resp = requests.post(f"{BASE_URL}/products", headers=HEADERS, json=payload, timeout=15)
    if resp.status_code not in (200, 201):
        print(f"  ❌ {p['name']}: {resp.text[:200]}")
        return None

External Transmission

Medium
Category
Data Exfiltration
Confidence
80% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/create_whop_products.py (reported line 65)May include surrounding context.

python
"price": p["price"],
            "visibility": "public",
        }
        plan_resp = requests.post(f"{BASE_URL}/v2/plans", headers=HEADERS, json=plan_payload, timeout=15)
        if plan_resp.status_code in (200, 201):
            plan = plan_resp.json().get("data", {}) or plan_resp.json()
            plan_id = plan.get("id", "")

Tainted flow: 'plan_payload' from requests.post (line 58, network input) → requests.post (network output)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/create_whop_products.py (reported line 65)May include surrounding context.

python
"price": p["price"],
            "visibility": "public",
        }
        plan_resp = requests.post(f"{BASE_URL}/v2/plans", headers=HEADERS, json=plan_payload, timeout=15)
        if plan_resp.status_code in (200, 201):
            plan = plan_resp.json().get("data", {}) or plan_resp.json()
            plan_id = plan.get("id", "")

External Transmission

Medium
Category
Data Exfiltration
Confidence
80% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/create_whop_products.py (reported line 76)May include surrounding context.

python
"plan_id": plan_id,
                "allow_multiple_quantity": False,
            }
            checkout_resp = requests.post(f"{BASE_URL}/checkouts", headers=HEADERS, json=checkout_payload, timeout=15)
            if checkout_resp.status_code in (200, 201):
                checkout = checkout_resp.json().get("data", {}) or checkout_resp.json()
                checkout_url = checkout.get("url", checkout.get("checkout_url", ""))

External Transmission

Medium
Category
Data Exfiltration
Confidence
80% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/create_whop_products.py (reported line 87)May include surrounding context.

python
"plan_id": plan_id,
                "allow_multiple_quantity": False,
            }
            checkout_resp = requests.post(f"{BASE_URL}/checkouts", headers=HEADERS, json=checkout_payload, timeout=15)
            if checkout_resp.status_code in (200, 201):
                checkout = checkout_resp.json().get("data", {}) or checkout_resp.json()
                checkout_url = checkout.get("url", checkout.get("checkout_url", ""))

Tainted flow: 'checkout_payload' from requests.post (line 72, network input) → requests.post (network output)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/create_whop_products.py (reported line 76)May include surrounding context.

python
"plan_id": plan_id,
                "allow_multiple_quantity": False,
            }
            checkout_resp = requests.post(f"{BASE_URL}/checkouts", headers=HEADERS, json=checkout_payload, timeout=15)
            if checkout_resp.status_code in (200, 201):
                checkout = checkout_resp.json().get("data", {}) or checkout_resp.json()
                checkout_url = checkout.get("url", checkout.get("checkout_url", ""))

Tainted flow: 'checkout_payload' from requests.post (line 72, network input) → requests.post (network output)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/create_whop_products.py (reported line 87)May include surrounding context.

python
"plan_id": plan_id,
                "allow_multiple_quantity": False,
            }
            checkout_resp = requests.post(f"{BASE_URL}/checkouts", headers=HEADERS, json=checkout_payload, timeout=15)
            if checkout_resp.status_code in (200, 201):
                checkout = checkout_resp.json().get("data", {}) or checkout_resp.json()
                checkout_url = checkout.get("url", checkout.get("checkout_url", ""))

Static analysis

No suspicious patterns detected.