Back to skill

Security audit

Phy Telegram Bot Payments

Security checks for vulnerabilities and agentic risk

Overview

This payment skill is purpose-aligned but needs careful review because its sample webhook code can grant paid credits from unauthenticated Telegram payment requests and writes billing state based on untrusted data.

Install only after revising the payment server: authenticate Telegram webhooks with a secret token, validate pre-checkout and successful-payment data against server-side orders, use idempotency keys, strictly validate user IDs and path containment, pin dependencies, avoid direct public exposure of port 8001, and make payment wording transparent and locale-appropriate.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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
SKILL.md:185
Finding
Unauthenticated Telegram Webhook Allows Fraudulent Credit Grants<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md`, lines 185-210 **Vulnerability Type**: Unauthenticated webhook and untrusted payment data **Risk Level**: High ### Vulnerable Code ```python @app.post("/webhook/telegram") async def telegram_webhook(request: Request): data = await request.json() # Must answer pre_checkout_query within 10 seconds pq = data.get("pre_checkout_query") if pq: httpx.post( f"https://api.telegram.org/bot{BOT_TOKEN}/answerPreCheckoutQuery", json={"pre_checkout_query_id": pq["id"], "ok": True}, timeout=8, ) return {"ok": True} msg = data.get("message", {}) payment = msg.get("successful_payment") if payment: # payload format: "credits_20_697391377" parts = payment.get("invoice_payload", "").split("_") if len(parts) == 3 and parts[0] == "credits": credits = int(parts[1]) user_id = parts[2] total = add_credits(user_id, credits) notify_user(user_id, credits, total) return {"ok": True} ``` ### Technical Analysis The public Telegram webhook does not authenticate incoming requests. It does not verify Telegram's webhook secret header or otherwise establish that a request originated from Telegram. The handler treats the request body's `successful_payment` object as authoritative and directly derives both the number of credits and the target user from the attacker-controlled `invoice_payload`. It does not validate: - The webhook source - The Telegram payer identity - The expected invoice payload - The package identifier - The amount paid - The `XTR` currency - A Telegram payment charge identifier - Whether the transaction was previously processed Consequently, possession of a valid bot payment event is unnecessary. Any client that can reach the endpoint can construct a synthetic payment update. The absence of transaction-level idempotency also permits the same su ...[truncated 1323 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Set a cryptographically random `secret_token` when registering the Telegram webhook. 2. Verify the `X-Telegram-Bot-Api-Secret-Token` header using constant-time comparison before parsing or processing a request. 3. Reject any request with a missing or invalid secret. 4. Define payment packages exclusively on the server. The invoice payload should contain an opaque package or order identifier rather than an authoritative credit amount. 5. Validate the payer's Telegram ID, invoice payload, currency, and total amount against the server-side order record. 6. Use `telegram_payment_charge_id` as an idempotency key and store it in a transactional database with a unique constraint. 7. Apply the credit update and transaction-record insertion in one atomic database transaction. 8. Reject unknown packages, nonpositive credit values, mismatched users, unexpected currencies, and previously processed transactions. 9. Restrict the endpoint behind TLS, ingress filtering, rate limits, and request-size limits. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
SKILL.md:189
Finding
Pre-Checkout Queries Are Approved Without Price or Package Validation<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md`, lines 189-197 **Vulnerability Type**: Missing payment authorization validation **Risk Level**: High ### Vulnerable Code ```python # Must answer pre_checkout_query within 10 seconds pq = data.get("pre_checkout_query") if pq: httpx.post( f"https://api.telegram.org/bot{BOT_TOKEN}/answerPreCheckoutQuery", json={"pre_checkout_query_id": pq["id"], "ok": True}, timeout=8, ) return {"ok": True} ``` ### Technical Analysis The handler approves every object containing `pre_checkout_query` by returning `ok: True`. It does not validate the fields that bind a payment to an authorized product and customer, including: - `from.id` - `invoice_payload` - `currency` - `total_amount` - The existence and status of a corresponding server-side order - The package and credit quantity associated with the order A pre-checkout callback is the server's final opportunity to reject a stale, altered, incorrectly priced, or unauthorized invoice. Blind approval removes this security boundary. The issue is compounded by the lack of webhook authentication in the same handler. Although a forged request alone cannot cause Telegram to complete a payment, arbitrary approval logic demonstrates that no server-side payment policy is enforced at pre-checkout. ### Attack Path 1. A payment flow produces a pre-checkout query containing an invalid, stale, mismatched, or manipulated invoice payload. 2. Telegram delivers the pre-checkout query to the webhook. 3. The application checks only whether the `pre_checkout_query` field exists. 4. The application sends `answerPreCheckoutQuery` with `ok: True`. 5. The payment is permitted to continue without confirming that the payer, package, amount, currency, and order state match the application's records. 6. If the later successful-payment handler receives a corresponding payload, it derives credits from that payload rather than a validated server-side packa ...[truncated 523 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Before approving a pre-checkout query: 1. Authenticate the incoming Telegram webhook. 2. Look up the invoice or order using an opaque, unpredictable identifier from `invoice_payload`. 3. Confirm that the order exists, remains unpaid, has not expired, and belongs to `pq["from"]["id"]`. 4. Verify that `currency` is exactly the expected currency, such as `XTR`. 5. Verify that `total_amount` exactly equals the amount stored for the selected package. 6. Derive the eventual credit quantity from the server-side package definition, never from the client-visible payload. 7. Return `ok: False` and an appropriate error message for any mismatch. 8. Log rejected validation attempts without recording sensitive tokens or complete payment data. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
SKILL.md:135
Finding
Attacker-Controlled User Identifier Is Used in a Filesystem Path<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md`, lines 135-145 and 200-207 **Vulnerability Type**: Path traversal and unsafe file update **Risk Level**: Medium ### Vulnerable Code ```python def add_credits(user_id: str, credits: int): """Add credits to user's usage.json.""" usage_file = WORKSPACES / user_id / "usage.json" if usage_file.exists(): usage = json.loads(usage_file.read_text()) else: usage = {"daily_count": 0, "credits": 0, "tier": "free"} usage["credits"] = usage.get("credits", 0) + credits usage_file.write_text(json.dumps(usage, indent=2)) return usage["credits"] ``` The value passed as `user_id` is obtained from the untrusted invoice payload: ```python payment = msg.get("successful_payment") if payment: # payload format: "credits_20_697391377" parts = payment.get("invoice_payload", "").split("_") if len(parts) == 3 and parts[0] == "credits": credits = int(parts[1]) user_id = parts[2] total = add_credits(user_id, credits) notify_user(user_id, credits, total) ``` ### Technical Analysis The code inserts `user_id` directly into a path: ```python WORKSPACES / user_id / "usage.json" ``` No check requires the identifier to be a canonical decimal Telegram user ID. Path separators and traversal components are not rejected, and the resulting path is not resolved and checked for containment beneath `WORKSPACES`. Because `user_id` is derived from an unauthenticated request, an attacker can supply traversal-style values. The final file name remains `usage.json`, so exploitation is limited to compatible existing or writable directories. The function also does not create parent directories, meaning a target directory generally must already exist. These constraints reduce but do not eliminate the risk. The read-modify-write sequence is additionally non-atomic. Concurrent webhook deliveries can overwrite one another or corrupt billing state. ### Attack ...[truncated 1054 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Require `user_id` to match a strict canonical format, such as `^[0-9]{1,20}$`, before using it. 2. Derive the target user from authenticated Telegram fields and a server-side order record rather than from `invoice_payload`. 3. Resolve both the workspace root and candidate path before access. 4. Verify with `Path.is_relative_to()` or an equivalent containment check that the resolved candidate remains beneath the intended workspace root. 5. Reject absolute paths, separators, traversal components, null bytes, and unexpected Unicode representations. 6. Run the webhook service under a dedicated account with write access only to the required application-state directory. 7. Replace direct JSON read-modify-write operations with transactional database updates where possible. 8. If files must be used, apply per-user locking and write through a temporary file followed by an atomic rename. 9. Validate that the existing state is a regular file and avoid following unsafe symbolic links. ]]>

T08 · Insecure Dependencies

Warning
Location
SKILL.md:285
Finding
Runtime Installation Uses Unpinned Third-Party Dependencies<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md`, lines 285-290 **Vulnerability Type**: Unpinned runtime dependencies and unnecessary network exposure **Risk Level**: Medium ### Vulnerable Code ```bash # Install dependencies pip3 install fastapi uvicorn stripe httpx # Start payment server in background uvicorn payment_server:app --host 0.0.0.0 --port 8001 & ``` ### Technical Analysis The instructions install four packages without exact version constraints or cryptographic hashes. The versions resolved during deployment can therefore change over time without a corresponding project review. This produces non-reproducible deployments and exposes the environment to upstream package compromise, unexpected dependency resolution, breaking security changes, or malicious artifacts served through a compromised package source. The audit found no evidence that the named packages are themselves malicious; the risk arises from the unsafe installation process. The service is then bound to `0.0.0.0`, making port 8001 available on every network interface unless external firewall or container controls restrict it. This materially increases exposure of the unauthenticated webhook vulnerabilities. ### Attack Path 1. A deployment follows the Skill instructions and runs the unconstrained `pip3 install` command. 2. Package resolution selects whatever package versions are current in the configured index at that time. 3. A compromised, unexpectedly changed, or incompatible package or transitive dependency is downloaded and installed. 4. Installation hooks or imported package code executes with the privileges of the deployment process. 5. The service starts on every network interface, potentially making both the application and any compromised dependency reachable from untrusted networks. ### Impact Assessment A compromised dependency could execute code with the privileges of the account running `pip3` or the webhook server. Depending on deployment privileges, ...[truncated 421 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin direct and transitive dependencies to reviewed versions in a lockfile. 2. Require cryptographic hashes during installation, such as with `pip install --require-hashes`. 3. Obtain packages only from an explicitly configured trusted index. 4. Build and scan a versioned deployment image rather than installing packages during application startup. 5. Regularly scan the lockfile and deployment image for known vulnerabilities. 6. Run installation and application processes as a dedicated unprivileged user. 7. Bind the service to localhost or a private interface when a reverse proxy is used. 8. Place the service behind a TLS-enabled reverse proxy and enforce firewall or security-group restrictions. 9. Do not expose port 8001 directly to the public Internet. 10. Separate build-time privileges from runtime privileges and provide only the secrets required by the running service. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • YARA SignaturesMalware Match, Webshell Match, Cryptominer Match
  • 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 (10)

YARA rule 'agent_skill_credential_exfiltration_webhook': AI agent skill credential harvesting followed by webhook or external exfiltration [agent_skills]

Critical
Category
YARA Match
Content
`payment_server.py` (FastAPI, runs on port 8001)

```python
"""
Unified payment webhook server.
Handles: Stripe webhooks + Telegram Stars pre_checkout_query + successful_payment
Run: uvicorn payment_server:app --host 0.0.0.0 --port 8001
"""
import json
import os
import pathlib
import httpx
from fastapi import FastAPI, Request, HTTPException
import stripe

app = FastAPI()
WORKSPACES = pathlib.Path(os.environ.get("WORKSPACES_DIR", "/workspaces"))
BOT_TOKEN = os.environ["TELEGRAM_BOT_TOKEN"]
STRIPE_WEBHOOK_SECRET = os.environ.get("STRIPE_WEBHOOK_SECRET", "")
stripe.api_key = os.environ.get("STRIPE_SECRET_KEY", "")

CREDIT_PACKAGES = {
    "price_20":  {"credits": 20,  "price_id": "price_xxx"},  # replace with real Stripe price IDs
    "price_50":  {"credits": 50,  "price_id": "price_yyy"},
    "price_100": {"credits": 100, "price_id": "price_zzz"},
}


def add_credits(user_id: str, credits: int):
    """Add credits to user's usage.json."""
    usage_file = WORKSPACES / user_id / "usage.js
Confidence
85% confidence
Finding
YARA rule matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).

External Transmission

Medium
Category
Data Exfiltration
Content
def notify_user(user_id: str, credits_added: int, total_credits: int):
    """Send Telegram message to user after successful payment."""
    httpx.post(
        f"https://api.telegram.org/bot{BOT_TOKEN}/sendMessage",
        json={
            "chat_id": user_id,
            "text": f"✅ 充值成功!\n\n+{credits_added} 张图片额度\n剩余总额度:{total_credits} 张\n\n直接告诉我你想要什么吧 👇",
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
def notify_user(user_id: str, credits_added: int, total_credits: int):
    """Send Telegram message to user after successful payment."""
    httpx.post(
        f"https://api.telegram.org/bot{BOT_TOKEN}/sendMessage",
        json={
            "chat_id": user_id,
            "text": f"✅ 充值成功!\n\n+{credits_added} 张图片额度\n剩余总额度:{total_credits} 张\n\n直接告诉我你想要什么吧 👇",
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
def notify_user(user_id: str, credits_added: int, total_credits: int):
    """Send Telegram message to user after successful payment."""
    httpx.post(
        f"https://api.telegram.org/bot{BOT_TOKEN}/sendMessage",
        json={
            "chat_id": user_id,
            "text": f"✅ 充值成功!\n\n+{credits_added} 张图片额度\n剩余总额度:{total_credits} 张\n\n直接告诉我你想要什么吧 👇",
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
def notify_user(user_id: str, credits_added: int, total_credits: int):
    """Send Telegram message to user after successful payment."""
    httpx.post(
        f"https://api.telegram.org/bot{BOT_TOKEN}/sendMessage",
        json={
            "chat_id": user_id,
            "text": f"✅ 充值成功!\n\n+{credits_added} 张图片额度\n剩余总额度:{total_credits} 张\n\n直接告诉我你想要什么吧 👇",
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
def notify_user(user_id: str, credits_added: int, total_credits: int):
    """Send Telegram message to user after successful payment."""
    httpx.post(
        f"https://api.telegram.org/bot{BOT_TOKEN}/sendMessage",
        json={
            "chat_id": user_id,
            "text": f"✅ 充值成功!\n\n+{credits_added} 张图片额度\n剩余总额度:{total_credits} 张\n\n直接告诉我你想要什么吧 👇",
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The embedded code sends a hard-coded Chinese success message, and later sections prescribe Chinese-only paywall wording. This imposes a specific language on end users without offering a language choice or documenting a justified locale restriction, which is a natural-language policy concern.

Natural-Language Policy Violations

Medium
Confidence
93% confidence
Finding
The AGENTS.md snippet mandates specific Chinese phrases for quota and payment messaging and forbids alternative explanations. There is no indication that users can choose their language or that the skill is limited to a Chinese-only deployment context.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The AGENTS.md behavior explicitly says not to explain pricing logic and not to mention what Stripe or Stars are when triggering payment guidance. Because this markdown file defines a user-facing payment workflow that affects user spending, omitting basic disclosure about payment methods can leave users without adequate warning or context before interacting with purchase buttons.

Intent-Code Divergence

Medium
Confidence
94% confidence
Finding
The documentation says that when using Stripe Payment Links, credits can be stored in Product metadata and the webhook should read them from line_items. However, the actual webhook implementation at L171-L178 only reads `session.get("metadata", {}).get("credits", 0)` and never retrieves `line_items` or product metadata. This is an active contradiction between the documented integration behavior and the code path that actually grants credits.

Static analysis

No suspicious patterns detected.