Back to skill

Security audit

Crypto Price Skill

Security checks for vulnerabilities and agentic risk

Overview

This paid crypto price skill has a coherent purpose, but its billing path is unsafe and under-scoped enough to require review before installation.

Review this skill carefully before installing. It may contact CoinGecko and SkillPay, and its billing implementation exposes an API key, trusts caller-provided billing identities, and may charge before delivering a result. The publisher should rotate the key, remove it from the package, bind charges to authenticated platform identity, validate requests before charging, and fail closed on billing errors.

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

T09 · Insecure Skill Coding Practices

Error
Location
handler.py:10
Finding

Hardcoded SkillPay API Credential Exposed in Source Code and Documentation

Content
View full analysis
Remediation
View remediation

T05 · Unauthorized Access and Privilege Escalation

Error
Location
handler.py:35
Finding

Caller-Controlled User Identifier Is Trusted for Billing

Content
View full analysis
dict: """Charge user via SkillPay""" try: payload = { "api_key": SKILLPAY_API_KEY, "user_id": user_id, "amount": PRICE_USDT, "skill_id": SKILL_ID, "currency": "USDT", "description": "Crypto price query" } 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) ``` The handler accepts the identifier directly from its caller: ```python def handle(input_text: str, user_id: str = "default") -> dict: """Main handler""" crypto = extract_crypto(input_text) charge_result = charge_user(user_id) ``` The command-line entry point also allows arbitrary selection of the billing identifier: ```python user_id = sys.argv[2] if len(sys.argv) > 2 else "cli" print(json.dumps(handle(user_input, user_id), indent=2, ensure_ascii=False)) ``` ### Technical Analysis The billing subject is selected using a raw `user_id` parameter supplied by the caller. No authenticated session, signed identity claim, ownership check, or authorization decision binds that identifier to the person initiating the request. Consequently, the code crosses an access-control boundary using an untrusted object identifier. If SkillPay relies on the skill's API key and supplied `user_id` when authorizing a charge, possession or discovery of another user's identifier may be sufficient to target that user. The current file also contains an indentation error and an undefined `SKILL_ID`, which prevent normal billing execution as written. Thes ...[truncated 1162 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
handler.py:35
Finding

Billing Exceptions Fail Open and Enable Payment Bypass

Content
View full analysis
dict: """Charge user via SkillPay""" try: payload = { "api_key": SKILLPAY_API_KEY, "user_id": user_id, "amount": PRICE_USDT, "skill_id": SKILL_ID, "currency": "USDT", "description": "Crypto price query" } 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: return {"success": True, "data": response.json()} return {"success": False, "error": response.text} except Exception as e: return {"success": True, "demo": True, "error": str(e)} ``` The handler permits execution when the exception result includes `demo`: ```python charge_result = charge_user(user_id) if not charge_result.get("success") and not charge_result.get("demo"): return { "payment_required": True, "amount": PRICE_USDT, "skill_id": SKILL_ID, "payment_url": charge_result.get("payment_url", "https://skillpay.me") } ``` It then labels the unpaid operation as a demo: ```python result["payment_status"] = "free_demo" if charge_result.get("demo") else "paid" ``` ### Technical Analysis Every exception raised while constructing or submitting a billing request is converted into `{"success": True, "demo": True}`. The main handler interprets that result as authorization to continue. This conflates an operational failure with an explicitly approved free transaction. Exceptions can result from timeouts, DNS failures, TLS errors, malformed responses, programming errors, or missing configuratio ...[truncated 1169 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
handler.py:88
Finding

User Is Charged Before Input and Service-Result Validation

Content
View full analysis
dict: """Main handler""" crypto = extract_crypto(input_text) charge_result = charge_user(user_id) if not charge_result.get("success") and not charge_result.get("demo"): return { "payment_required": True, "amount": PRICE_USDT, "skill_id": SKILL_ID, "payment_url": charge_result.get("payment_url", "https://skillpay.me") } if "top" in input_text.lower() or "list" in input_text.lower() or "all" in input_text.lower(): result = get_top_coins() elif crypto: result = get_crypto_price(crypto) else: return {"error": "Please specify a crypto", "usage": "Example: 'Bitcoin price' or 'Top cryptos'"} result["payment_status"] = "free_demo" if charge_result.get("demo") else "paid" return result ``` ### Technical Analysis `charge_user()` is called before the handler determines whether the request is valid and before CoinGecko successfully returns the requested information. A successful charge may therefore be followed by: - Rejection because no cryptocurrency was specified. - An unsupported cryptocurrency response. - A CoinGecko timeout or network exception. - A non-success response from CoinGecko. - A response that does not contain the requested asset. Charging before validation and fulfillment breaks transaction atomicity: payment can be committed even though the paid service is not delivered. ### Attack Path 1. A caller submits empty, malformed, or unsupported cryptocurrency input, or submits a valid request while CoinGecko is unavailable. 2. The handler calls `charge_user()` before validating the input or obtaining a result. 3. SkillPay confirms and commits the charge. 4. Input validation or the upst ...[truncated 901 chars]
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (9)

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The skill states it uses the CoinGecko API but does not clearly warn users that their requests will be sent to an external third-party service. This creates a transparency and privacy issue because users may unknowingly transmit their query content to an outside provider, and the risk is heightened here because the skill is also monetized and includes sensitive operational material such as an exposed API key.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
86% confidence
Finding

The usage example "Crypto prices" is overly broad and could cause the skill to trigger on generic cryptocurrency-related requests rather than clear user intent to invoke this paid third-party skill. In a paid skill context, broad matching increases the risk of unintended invocation, unnecessary external API calls, and unexpected charges to the user.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The skill is presented as a simple crypto price lookup tool, but its main workflow also performs billing through a third-party service before returning results. That hidden monetization behavior is risky because users and integrators may invoke the skill expecting a read-only information query, while the code transmits identifiers and attempts a charge as a side effect.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The code sends user_id, amount, skill identifier, and description to an external billing API without any visible disclosure, consent flow, or minimization in this file. In a skill context, silent transmission of user-linked billing data to a third party raises privacy and compliance concerns, especially when the skill appears informational rather than transactional.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The billing function claims to charge the user, but on any exception it returns success with a demo flag instead of a clear failure. This can mask payment-system errors, create inconsistent billing state, and allow the skill to serve results without a successful charge while falsely signaling that everything is fine.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
97% confidence
Finding

This line transmits billing data to an external payment service and includes a hard-coded API key in both the payload and header path of the request flow. Combined with the embedded secret in source code, this creates a serious risk of unauthorized billing activity, credential leakage, and abuse of the payment account if the code is exposed.

Content

Scanner excerpt · handler.py (reported line 44)May include surrounding context.

python
"description": "Crypto price query"
        }
            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:
            return {"success": True, "data": response.json()}
        return {"success": False, "error": response.text}

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

python
"""Get crypto price from CoinGecko"""
    try:
        coin_id = CRYPTO_MAP.get(crypto.lower(), crypto.lower())
        url = f"https://api.coingecko.com/api/v3/simple/price?ids={coin_id}&vs_currencies=usd&include_24hr_change=true"
        response = requests.get(url, timeout=10)
        
        if response.status_code == 200:

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

python
"""Get crypto price from CoinGecko"""
    try:
        coin_id = CRYPTO_MAP.get(crypto.lower(), crypto.lower())
        url = f"https://api.coingecko.com/api/v3/simple/price?ids={coin_id}&vs_currencies=usd&include_24hr_change=true"
        response = requests.get(url, timeout=10)
        
        if response.status_code == 200:

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
84% confidence
Finding

The skill constructs a CoinGecko request from user-provided crypto text and sends it over the network, but this file provides no visible user warning that input-derived data will be transmitted to a third-party service. The docstring is developer-facing and does not constitute a user-facing disclosure.

Content

No source excerpt is available for this finding.