Back to skill

Security audit

庄家异动探测器

Security checks for vulnerabilities and agentic risk

Overview

This skill appears to provide the promised Polymarket market data, but it includes a built-in payment API credential and weak billing controls that need review before installation.

Review this skill before installing or deploying it. Remove and rotate the embedded SkillPay key, require a deployment-provided secret, restrict the SkillPay API base to the intended provider, update the pinned requests dependency, and do not rely on the current charge_id flow for strong per-use payment enforcement.

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

T09 · Insecure Skill Coding Practices

Error
Location
main.py:10
Finding

Hardcoded SkillPay API Credential Can Be Transmitted to a Configurable Network Endpoint

Content
View full analysis

Vulnerability Details

File Location: main.py:10-11, with credential transmission sinks at main.py:203-218 and main.py:230-235
Vulnerability Type: Hardcoded secret and unsafe credential destination configuration
Risk Level: High

Vulnerable Code

python
SKILLPAY_API_KEY = os.getenv("SKILLPAY_API_KEY", "sk_8b36c2ca9e774eb0243752f907b086e78c8af866a4088d3e3475113ed446b71")
SKILLPAY_API_BASE = os.getenv("SKILLPAY_API_BASE", "https://api.skillpay.me")

The embedded credential is transmitted through the following functions:

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

Technical Analysis

The application contains a live-looking SkillPay bearer credential as the default value of SKILLPAY_API_KEY. This contradicts the configuration in skill.yaml, which declares that environment variable as required. Anyone with access to the source package can recover and attempt to reuse the credential.

The credential is included in the Authorization header of requests ...[truncated 2343 chars]

Remediation
View remediation

Remediation Suggestions

  1. Revoke and rotate the exposed SkillPay credential immediately; source removal alone cannot invalidate copies already distributed.
  2. Remove the hardcoded fallback and require secret injection:
    python
    SKILLPAY_API_KEY = os.getenv("SKILLPAY_API_KEY")
    if not SKILLPAY_API_KEY:
        raise RuntimeError("SKILLPAY_API_KEY is required")
    
  3. Store the credential in a managed secret service or deployment secret facility rather than source control, images, or ordinary configuration files.
  4. Pin or allowlist the billing API origin. If custom endpoints are required for testing, permit them only in an explicit non-production mode.
  5. Require HTTPS and validate the parsed scheme and hostname before attaching the authorization header.
  6. Use a narrowly scoped provider credential that can perform only the charge operations required by this skill.
  7. Add automated secret scanning to commits and release artifacts.
  8. Avoid logging authorization headers, environment dumps, or complete outbound request objects.
  9. Establish periodic key rotation and monitoring for abnormal charge activity.

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
main.py:230
Finding

Caller-Supplied Paid Charge IDs Can Be Replayed to Bypass Per-Invocation Billing

Content
View full analysis

Vulnerability Details

File Location: main.py:230-259
Vulnerability Type: Insufficient payment authorization binding and replay protection
Risk Level: Medium

Vulnerable Code

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 API accepts a charge_id supplied entirely by the caller and treat ...[truncated 2097 chars]

Remediation
View remediation

Remediation Suggestions

  1. Create a server-side transaction record when generating each charge. Store the charge ID, expected amount, currency, product, creation time, caller or session identity, and consumption state.
  2. When checking payment, retrieve and validate the complete provider response rather than returning only its status.
  3. Require an exact match for:
    • Charge ID recorded by this service.
    • Expected amount of 0.01.
    • Expected currency of USDT.
    • Expected product or metadata.
    • Expected merchant or account identifier, if supplied by the provider.
    • Current authenticated caller or invocation token.
  4. Mark the charge as consumed in an atomic database transaction before returning paid data. Reject every subsequent use.
  5. Use database uniqueness constraints or conditional updates to prevent concurrent requests from consuming the same charge more than once.
  6. Introduce authenticated, short-lived invocation tokens so payment authorization is not based solely on a caller-provided charge identifier.
  7. Set expiration times for pending transactions and reject charges not created by the application.
  8. Prefer signed provider webhooks with signature verification for authoritative payment completion, while retaining server-to-server verification where necessary.
  9. Add tests covering replay, concurrent redemption, wrong amount, wrong currency, foreign charge IDs, expired charges, and caller mismatch.
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • 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
Findings (12)

Tainted flow: 'url' from os.getenv (line 233, credential/environment) → requests.get (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 · main.py (reported line 176)May include surrounding context.

python
]
    for url in candidates:
        try:
            r = requests.get(url, timeout=15)
            if r.status_code != 200:
                continue
            payload = r.json()

Tainted flow: 'url' from os.getenv (line 233, 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 · main.py (reported line 218)May include surrounding context.

python
"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()

Tainted flow: 'url' from os.getenv (line 233, credential/environment) → requests.get (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 · main.py (reported line 235)May include surrounding context.

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

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.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The file's user-facing headings and descriptions are entirely in Chinese, and there is no indication that the skill is region-specific or that users can opt into this language. This can violate language/locale policy when a skill forces a specific language without user choice.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The skill introduces an unrelated third-party payment dependency and credential usage beyond the apparent market-data use case. This expands the trust boundary and may expose operators to financial or privacy risk if they deploy the skill without realizing it talks to an external billing provider.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
99% confidence
Finding

A hardcoded fallback API key is a real secret-management vulnerability: anyone with source access can reuse the credential, create or inspect charges, and potentially incur costs or access payment data. Combined with undisclosed outbound payment calls, this makes the skill more dangerous because deployment immediately enables third-party financial operations even when the operator did not provide their own key.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The skill's apparent purpose is fetching market movers, but it embeds payment creation and payment-status polling logic that is not essential to core data retrieval. Hidden monetization and third-party charge handling increase risk because users and operators may unknowingly trigger billing workflows or route metadata to a payment processor.

Content

No source excerpt is available for this finding.

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 · main.py (reported line 218)May include surrounding context.

python
"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()

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
95% confidence
Finding

The dependency pins requests to 2.31.0, which is identified by the scanner as having multiple published advisories. Keeping a known vulnerable HTTP client library in a network-facing skill can expose the application to credential leakage, TLS/session validation issues, or other request-handling flaws depending on how the library is used.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The manifest description is written as a fixed Chinese-language instruction: "获取 Polymarket 活跃市场中变动绝对值最大的 3 个异动话题," with no indication that users may choose another language or that the skill is intended only for a Chinese-speaking locale. This creates a natural-language locale policy concern because it appears to impose a language preference without opt-in or justification.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
76% confidence
Finding

The response field name "api_connected" implies an actual connectivity check, but the code only evaluates whether SKILLPAY_API_KEY is not None. Because a default key string is supplied earlier, this can indicate readiness/connection without verifying real access to the external service.

Content

No source excerpt is available for this finding.