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## 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
## 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## 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
## 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
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 233, 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
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 233, 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
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

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

Context-Inappropriate Capability

Medium
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.

Missing User Warnings

Medium
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.

Context-Inappropriate Capability

Medium
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.

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

Natural-Language Policy Violations

Medium
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.

Intent-Code Divergence

Low
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.

Static analysis

No suspicious patterns detected.