Back to skill

Security audit

庄家异动探测器

Security checks for vulnerabilities and agentic risk

Overview

The skill does what it claims, but its payment handling exposes a built-in payment credential and is not safely scoped.

Install only if you are comfortable reviewing and fixing the payment code first. The SkillPay key should be treated as compromised and removed from source, payment API hosts should be allowlisted, and charge IDs should be bound to a single paid invocation before this is used with real billing.

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

Error
Location
main.py:10
Finding
Hard-Coded SkillPay Credential Can Be Exposed to a Configurable Network Destination<![CDATA[ ## Vulnerability Details **File Location**: `main.py:10-12`, `main.py:207-225`, and `main.py:232-242` **Vulnerability Type**: Hard-coded secret and unsafe credential transmission **Risk Level**: High ### Vulnerable Code ```python SKILLPAY_API_KEY = "sk_8b36c2ca9e774eb0243752f907b086e78c8af866a4088d3e3475113ed446b71" SKILLPAY_API_BASE = os.getenv("SKILLPAY_API_BASE", "https://api.skillpay.me") SKILLPAY_WEB_BASE = os.getenv("SKILLPAY_WEB_BASE", "https://pay.skillpay.me") ``` ```python def create_skillpay_charge(amount: str, currency: str) -> Tuple[str, str]: if not SKILLPAY_API_KEY: raise HTTPException(status_code=400, detail="Missing SKILLPAY_API_KEY") url = f"{SKILLPAY_API_BASE.rstrip('/')}/v1/charges" headers = { "Authorization": f"Bearer {SKILLPAY_API_KEY}", "Content-Type": "application/json", } body = { "amount": amount, "currency": currency, "title": "OpenClaw Skill Payment", "description": "Polymarket Movers x3", } r = requests.post(url, json=body, headers=headers, timeout=20) if r.status_code not in (200, 201): raise HTTPException(status_code=502, detail="SkillPay create charge failed") data = r.json() cid = str(data.get("id") or data.get("charge_id") or "") purl = data.get("payment_url") if not purl and cid: purl = f"{SKILLPAY_WEB_BASE.rstrip('/')}/checkout/{cid}" if not cid or not purl: raise HTTPException(status_code=502, detail="Invalid SkillPay response") return cid, purl ``` ```python def get_skillpay_status(charge_id: str) -> str: if not SKILLPAY_API_KEY: raise HTTPException(status_code=400, detail="Missing SKILLPAY_API_KEY") url = f"{SKILLPAY_API_BASE.rstrip('/')}/v1/charges/{charge_id}" headers = {"Authorization": f"Bearer {SKILLPAY_API_KEY}"} r = requests.get(url, headers=headers, timeout=20) if r.status_code != 200: raise HTTPException(status_code=502, detail="Sk ...[truncated 2534 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Revoke and rotate the exposed credential immediately; it must be treated as compromised. 2. Remove the credential from source code and repository history. 3. Read the key only from a protected runtime secret: ```python SKILLPAY_API_KEY = os.environ["SKILLPAY_API_KEY"] ``` 4. Store the key in a managed secret service or deployment secret, with access limited to this service. 5. Pin the production payment API to an exact HTTPS origin. If endpoint configurability is required for testing, validate the scheme and hostname against an explicit allowlist and prohibit redirects to untrusted origins. 6. Use separate, least-privileged credentials for development, testing, and production. 7. Restrict the provider credential to only the charge operations required by this Skill, if SkillPay supports scoped keys. 8. Add automated secret scanning and deployment checks that reject committed credentials and unapproved payment API origins. 9. Avoid logging authorization headers, secret values, or full provider responses. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
main.py:244
Finding
Replayable Charge IDs Permit Payment-Gate Bypass<![CDATA[ ## Vulnerability Details **File Location**: `main.py:19-20`, `main.py:232-265` **Vulnerability Type**: Improper payment authorization and replay protection **Risk Level**: Medium ### Vulnerable Code ```python class InvokeRequest(BaseModel): charge_id: Optional[str] = None ``` ```python def get_skillpay_status(charge_id: str) -> str: if not SKILLPAY_API_KEY: raise HTTPException(status_code=400, detail="Missing SKILLPAY_API_KEY") url = f"{SKILLPAY_API_BASE.rstrip('/')}/v1/charges/{charge_id}" headers = {"Authorization": f"Bearer {SKILLPAY_API_KEY}"} r = requests.get(url, headers=headers, timeout=20) if r.status_code != 200: raise HTTPException(status_code=502, detail="SkillPay status query failed") data = r.json() status = str(data.get("status") or data.get("state") or "").lower() return status @app.post("/invoke", response_model=MoversResponse) def invoke(req: InvokeRequest) -> MoversResponse: if not req.charge_id: cid, purl = create_skillpay_charge(PRICE_AMOUNT, PRICE_CURRENCY) return MoversResponse(requires_payment=True, charge_id=cid, payment_url=purl, status="pending") status = "" for _ in range(10): status = get_skillpay_status(req.charge_id) if status in ["paid", "succeeded", "success", "completed"]: break if status in ["failed", "canceled", "expired"]: return MoversResponse(requires_payment=True, charge_id=req.charge_id, payment_url=None, status=status) time.sleep(3) if status not in ["paid", "succeeded", "success", "completed"]: return MoversResponse(requires_payment=True, charge_id=req.charge_id, payment_url=None, status=status or "pending") markets = fetch_markets() top10 = pick_active_top10(markets) movers = compute_movers(top10) return MoversResponse(requires_payment=False, data=movers, status="ok") ``` ### Technical Analysis The endpoint treats a caller-supplied `charge_id` ...[truncated 2012 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Create a server-side record for every charge, including: - Provider charge ID. - Expected amount and currency. - Product or Skill identifier. - Creation and expiration times. - Request or authenticated-client binding. - Consumption status. 2. Retrieve and verify the complete provider response rather than returning only its status. Confirm the amount, currency, merchant account, product metadata, and final settlement state. 3. Reject charge IDs that were not created and recorded by this service. 4. Bind each charge to an authenticated caller or to a cryptographically random, short-lived invocation token. 5. Mark a successful charge as consumed in an atomic database transaction before returning paid content. 6. Reject already-consumed, expired, canceled, refunded, mismatched, or unknown charges. 7. Protect charge IDs as authorization artifacts: do not place them in logs, public URLs, or analytics. 8. Add automated tests covering reuse, concurrent replay, amount mismatch, currency mismatch, unknown IDs, refunds, and expiration. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (12)

Tainted flow: 'url' from os.getenv (line 231, credential/environment) → requests.get (network output)

Critical
Category
Data Flow
Content
]
    for url in candidates:
        try:
            r = requests.get(url, timeout=15)
            if r.status_code != 200:
                continue
            payload = r.json()
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'url' from os.getenv (line 231, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Content
"title": "OpenClaw Skill Payment",
        "description": "Polymarket Movers x3",
    }
    r = requests.post(url, json=body, headers=headers, timeout=20)
    if r.status_code not in (200, 201):
        raise HTTPException(status_code=502, detail="SkillPay create charge failed")
    data = r.json()
Confidence
92% confidence
Finding
The payment API base URL is taken from an environment variable and then used to send an authenticated POST request containing the bearer token. If an attacker can influence deployment environment variables, they can redirect this request to an attacker-controlled endpoint and capture the hard-coded SkillPay secret, turning this into credential exfiltration/SSRF. The presence of a hard-coded live-looking API key materially increases the danger.

Tainted flow: 'url' from os.getenv (line 231, credential/environment) → requests.get (network output)

Critical
Category
Data Flow
Content
raise HTTPException(status_code=400, detail="Missing SKILLPAY_API_KEY")
    url = f"{SKILLPAY_API_BASE.rstrip('/')}/v1/charges/{charge_id}"
    headers = {"Authorization": f"Bearer {SKILLPAY_API_KEY}"}
    r = requests.get(url, headers=headers, timeout=20)
    if r.status_code != 200:
        raise HTTPException(status_code=502, detail="SkillPay status query failed")
    data = r.json()
Confidence
92% confidence
Finding
The status-check request also builds its destination from an environment-controlled base URL and includes the bearer credential in the Authorization header. If that base URL is changed to an attacker-controlled host, every status lookup leaks the payment credential and permits unauthorized use of the payment account. In this skill, the risk is elevated because the credential is embedded directly in the file.

Context-Inappropriate Capability

High
Confidence
98% confidence
Finding
A hard-coded external payment credential is present in source code and enables undeclared billing capability. Anyone with code access, logs, backups, or package artifacts can extract and abuse the secret to create charges, inspect payment data, or impersonate the service against the payment provider. Because it is a payment credential, compromise can have direct financial consequences.

Possible Typosquatting: 'uvicorn' resembles popular package 'gunicorn'

High
Category
Supply Chain
Confidence
70% confidence
Finding
Package name closely resembles a popular package, suggesting possible typosquatting. Attackers publish malicious packages with similar names to trick developers into installing them.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The file’s natural-language instructions and descriptions are exclusively in Chinese, and there is no indication that users can opt into another language or that the skill is intentionally limited to a Chinese-speaking context. Under the language/locale policy, forcing a specific language without user choice is a natural-language policy violation.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The code embeds a payment API credential without any meaningful user-facing disclosure about billing trust, operator identity, or security posture. This combination creates a hidden monetization surface and increases the likelihood of misuse or undisclosed billing behavior, especially in a skill with no metadata or provenance. The hard-coded credential also broadens exposure if the code is shared.

Context-Inappropriate Capability

Medium
Confidence
87% confidence
Finding
The skill includes payment-processing logic and gates data access on payment despite the skill metadata providing no declared justification or trust context. In an agent-skill ecosystem, introducing billing capability without clear disclosure increases the risk of unauthorized charging flows, user confusion, and hidden monetization pathways. This context makes the capability more suspicious, not less.

Missing User Warnings

Medium
Confidence
87% confidence
Finding
The skill sends billing-related data to an external payment API but provides only minimal endpoint usage text and no substantive disclosure about what data is transmitted, who operates the payment service, or what trust assumptions apply. In a skill context, hidden outbound billing integrations are risky because users and platform operators may not expect payment/account interactions from a data-fetching tool.

External Transmission

Medium
Category
Data Exfiltration
Content
"title": "OpenClaw Skill Payment",
        "description": "Polymarket Movers x3",
    }
    r = requests.post(url, json=body, headers=headers, timeout=20)
    if r.status_code not in (200, 201):
        raise HTTPException(status_code=502, detail="SkillPay create charge failed")
    data = r.json()
Confidence
80% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Known Vulnerable Dependency: requests==2.31.0 — 6 advisory(ies): 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); CVE-2026-25645 (Requests has Insecure Temp File Reuse in its extract_zipped_paths() utility func) +3 more

Medium
Category
Supply Chain
Confidence
97% confidence
Finding
The dependency is pinned to requests==2.31.0, which has multiple published advisories, including issues involving credential leakage via .netrc handling and improper verification behavior in some session flows. Using a version with known CVEs creates real exposure if the application makes outbound HTTP requests, and this skill's inclusion of FastAPI plus requests makes network use plausible rather than theoretical.

Natural-Language Policy Violations

Medium
Confidence
96% confidence
Finding
The manifest description is written to instruct the skill in Chinese only, with no indication that users can select another language or that the skill is intended exclusively for a Chinese-speaking or region-specific audience. This creates a natural-language locale policy concern because it imposes a language preference without explicit user opt-in.

Static analysis

No suspicious patterns detected.