Back to skill

Security audit

Weather Skill

Security checks for vulnerabilities and agentic risk

Overview

This paid weather skill is mostly coherent, but it exposes a live-looking billing API key and performs external billing/weather requests with limited consent and scoping.

Review before installing. The skill sends a user identifier to SkillPay and a city/location string to wttr.in, charges 0.001 USDT per successful non-demo call, and includes a public hardcoded billing API key that should be rotated and removed. Install only if you accept the third-party data sharing and the publisher fixes or clearly scopes the billing behavior.

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
handler.py:13
Finding

Hardcoded SkillPay API Key Exposed in Source Code and Documentation

Content
View full analysis

Vulnerability Details

File Location: handler.py:13 and SKILL.md:24
Vulnerability Type: Hardcoded credential exposure
Risk Level: High

The same SkillPay API key is embedded in both the executable source code and the public-facing skill documentation.

python
# handler.py:13
SKILLPAY_API_KEY = os.environ.get("SKILLPAY_API_KEY", "sk_93c5ff38cc3e6112623d361fffcc5d1eb1b5844eac9c40043b57c0e08f91430e")
markdown
<!-- SKILL.md:22-25 -->
This skill uses SkillPay for payment processing:
- API Key: sk_93c5ff38cc3e6112623d361fffcc5d1eb1b5844eac9c40043b57c0e08f91430e
- Price: 0.001 USDT per call

The credential is subsequently used to authenticate billing requests:

python
headers = {
    "Content-Type": "application/json",
    "X-API-Key": SKILLPAY_API_KEY
}
response = requests.post(f"{SKILLPAY_API_URL}/charge", json=payload, headers=headers, timeout=10)

Technical Analysis

A secret embedded in a distributed skill package cannot be considered confidential. Any user with access to the package, source repository, build artifact, logs containing the source, or documentation can recover the API key.

Although an environment variable can override the value, the hardcoded fallback means the exposed credential is used whenever SKILLPAY_API_KEY is absent. The documentation independently discloses the same value. The key has the appearance and usage of an authentication credential, but the audit did not attempt to verify whether it remains active.

Attack Path

  1. An attacker downloads or otherwise obtains the skill package.
  2. The attacker reads handler.py or SKILL.md and extracts the SkillPay API key.
  3. The attacker constructs requests to the SkillPay billing API and supplies the key through the X-API-Key header.
  4. If the key remains active, the attacker invokes any billing operations permitted by its server-side privileges.
  5. Requests made with the shared key ...[truncated 581 chars]
Remediation
View remediation

Remediation Suggestions

  1. Revoke and rotate the exposed API key immediately.
  2. Remove the key from handler.py, SKILL.md, repository history, release artifacts, examples, and cached documentation.
  3. Require the credential to be injected through a managed secret store or protected runtime environment variable.
  4. Do not provide a credential fallback:
    python
    SKILLPAY_API_KEY = os.environ.get("SKILLPAY_API_KEY")
    if not SKILLPAY_API_KEY:
        raise RuntimeError("SKILLPAY_API_KEY is required")
    
  5. Use a narrowly scoped key that can access only the required billing operation.
  6. Add secret-scanning checks to local hooks and CI pipelines to prevent future credential commits.
  7. Review billing logs for unauthorized activity involving the exposed key and establish periodic key rotation.

T09 · Insecure Skill Coding Practices

Warning
Location
handler.py:25
Finding

Billing Exceptions Fail Open and Grant Unpaid Service Access

Content
View full analysis

Vulnerability Details

File Location: handler.py:25-47 and handler.py:80-95
Vulnerability Type: Fail-open payment authorization
Risk Level: Medium

The billing function marks all exceptions as successful demo transactions:

python
def charge_user(user_id: str) -> dict:
    """
    Charge user via SkillPay before providing the service.
    """
    if not SKILLPAY_API_KEY or not SKILL_ID:
        return {"success": True, "demo": True, "error": "SKILLPAY not configured"}

    try:
        payload = {
            "user_id": user_id,
            "skill_id": SKILL_ID,
            "amount": float(PRICE_USDT)
        }
        headers = {
            "Content-Type": "application/json",
            "X-API-Key": SKILLPAY_API_KEY
        }
        response = requests.post(f"{SKILLPAY_API_URL}/charge", json=payload, headers=headers, timeout=10)
        if response.status_code == 200:
            data = response.json()
            if data.get("success"):
                return {"success": True, "balance": data.get("balance")}
            else:
                return {"success": False, "balance": data.get("balance"), "payment_url": data.get("payment_url")}
        else:
            return {"success": False, "error": response.text}
    except Exception as e:
        return {"success": True, "error": str(e), "demo": True}

The handler interprets this result as authorization and continues to provide the paid service:

python
def handle(input_text: str, user_id: str = "default") -> dict:
    city = extract_city(input_text)
    if not city:
        return {
            "error": "Please provide a city name",
            "usage": "Example: 'What's the weather in Tokyo?' or 'Temperature in Beijing'"
        }
    charge_result = charge_user(user_id)
    if not charge_result.get("success") and not charge_result.get("demo"):
        return {
            "payment_requi
...[truncated 2269 chars]
Remediation
View remediation

Remediation Suggestions

  1. Fail closed whenever billing cannot be conclusively completed:
    python
    except requests.RequestException:
        return {"success": False, "error": "Billing service unavailable"}
    except (ValueError, KeyError, TypeError):
        return {"success": False, "error": "Invalid billing response"}
    
  2. Do not set success to true for exceptions or missing production configuration.
  3. Return a temporary service-unavailable response and do not call get_weather() unless billing explicitly confirms success.
  4. Permit demo mode only through an explicit, trusted deployment setting such as DEMO_MODE=true; keep it disabled by default in production.
  5. Validate the billing response schema and require an explicit successful transaction identifier before authorizing service.
  6. Log billing failures securely without returning raw exception details or upstream response bodies to callers.
  7. Add tests covering timeouts, DNS failures, malformed JSON, non-200 responses, absent credentials, and explicit payment rejection.
  8. Consider idempotency keys and transaction reconciliation to prevent inconsistent charging behavior during retries.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (8)

Tainted flow: 'payload' from os.environ.get (line 27, 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 · handler.py (reported line 36)May include surrounding context.

python
"Content-Type": "application/json",
            "X-API-Key": SKILLPAY_API_KEY
        }
        response = requests.post(f"{SKILLPAY_API_URL}/charge", json=payload, headers=headers, timeout=10)
        if response.status_code == 200:
            data = response.json()
            if data.get("success"):

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The skill states it uses wttr.in but does not clearly warn users that their requested location will be transmitted to a third-party external service. This creates a privacy and transparency issue because user queries may contain sensitive location information, and users are not given informed notice before data leaves the local system.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The invocation examples are broad natural-language phrases without clear activation boundaries, which can cause the skill to trigger unintentionally during unrelated conversation. In a paid skill, accidental invocation is more dangerous because it may lead to unintended charges and unnecessary disclosure of user-provided location data to the external weather service.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The skill advertises itself as a simple weather lookup, but it also performs a billing action before returning results. This mismatch is security-relevant because it conceals a monetization side effect from users and reviewers, undermining informed consent and making deceptive behavior easier to hide.

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

python
"Content-Type": "application/json",
            "X-API-Key": SKILLPAY_API_KEY
        }
        response = requests.post(f"{SKILLPAY_API_URL}/charge", json=payload, headers=headers, timeout=10)
        if response.status_code == 200:
            data = response.json()
            if data.get("success"):

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The code transmits user_id to an external billing service without any visible disclosure or consent flow. Even if user_id is not highly sensitive by itself, silently sharing identifiers with third parties creates privacy and trust risks and may violate platform or legal requirements.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
80% confidence
Finding

The file states 'Returns temperature in Celsius' as a fixed behavior. This can be a locale-related policy issue because it imposes one regional unit convention without user opt-in or a documented region-specific justification.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
84% confidence
Finding

The skill sends user-supplied city data to wttr.in without notifying the user that their query is being forwarded to an external service. While the city string is usually low sensitivity, external transmission of user input still has privacy implications, especially if users include more information than intended.

Content

No source excerpt is available for this finding.

Static analysis

Detected: suspicious.exposed_secret_literal

File appears to expose a hardcoded API secret or token.

Critical
Code
suspicious.exposed_secret_literal
Location
SKILL.md:26