Back to skill

Security audit

Signallink

Security checks for vulnerabilities and agentic risk

Overview

SignalLink is a disclosed Telegram webhook router, but one message-forwarding endpoint is unauthenticated and the default deployment can expose the relay broadly.

Install only if you are prepared to harden it before exposing it beyond localhost: require a non-empty WEBHOOK_SECRET on every POST endpoint including /webhook/raw, bind locally or place it behind a protected reverse proxy, avoid sending secrets or private incident data in webhook payloads, and restrict access to logs because full webhook bodies are logged.

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

T09 · Insecure Skill Coding Practices

Error
Location
App/webhook.py:64
Finding
Authentication Bypass on the Raw Webhook Endpoint<![CDATA[ ## Vulnerability Details **File Location**: `App/webhook.py:64-74` **Vulnerability Type**: Missing authentication on a security-sensitive endpoint **Risk Level**: High ### Vulnerable Code ```python @router.post("/webhook/raw") async def receive_raw_webhook(request: Request): """ Raw webhook endpoint — always uses key-value formatter. Useful for non-trading alerts (uptime monitors, CI/CD, etc.) """ try: payload: dict[str, Any] = await request.json() except Exception: raise HTTPException(status_code=400, detail="Invalid JSON payload") message = format_raw(payload) await send_message(message) return {"status": "ok"} ``` ### Technical Analysis The primary `/webhook` route validates the `X-Webhook-Secret` header before forwarding a message. The `/webhook/raw` route performs the same security-sensitive Telegram forwarding operation but never invokes `verify_secret` and does not otherwise authenticate the caller. Consequently, configuring `WEBHOOK_SECRET` does not protect all message-sending routes. This is an endpoint-level authorization bypass rather than merely an insecure default. ### Attack Path 1. An operator deploys the service and configures `WEBHOOK_SECRET`, expecting webhook forwarding to be protected. 2. The service exposes port 8000, including `/webhook/raw`. 3. An attacker submits arbitrary JSON without an `X-Webhook-Secret` header: ```http POST /webhook/raw HTTP/1.1 Content-Type: application/json {"Urgent security alert": "Visit the attacker-controlled link immediately"} ``` 4. The service formats the attacker-controlled JSON. 5. The service sends the resulting message to the configured Telegram chat using the victim's bot credentials. ### Impact Assessment A remote unauthenticated attacker can inject arbitrary messages into the configured Telegram chat. This permits forged operational or trading alerts, phishing content, alert-channel spam, social engineering, and c ...[truncated 252 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Require authentication on `/webhook/raw` using the same verification mechanism as `/webhook`. - Implement authentication as a shared FastAPI dependency or router-level dependency so new message-sending routes cannot accidentally omit it. - Use a non-optional header type when a secret is required. - Add tests proving that both endpoints return `401 Unauthorized` for missing or incorrect secrets. Example hardening pattern: ```python async def require_webhook_secret( x_webhook_secret: str | None = Header(default=None), ) -> None: if not verify_secret(x_webhook_secret): raise HTTPException(status_code=401, detail="Invalid webhook secret") @router.post("/webhook/raw", dependencies=[Depends(require_webhook_secret)]) async def receive_raw_webhook(request: Request): ... ``` ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
App/webhook.py:15
Finding
Public Fail-Open Webhook Authentication Configuration<![CDATA[ ## Vulnerability Details **File Location**: `App/webhook.py:15-19`; related configuration at `App/config.py:11-16` and `docker-compose.yml:6-10` **Vulnerability Type**: Fail-open authentication and insecure network exposure **Risk Level**: Medium ### Vulnerable Code `App/webhook.py:15-19`: ```python def verify_secret(provided: str) -> bool: """Constant-time comparison to prevent timing attacks.""" if not config.WEBHOOK_SECRET: return True # Secret not configured → open mode (not recommended for prod) return hmac.compare_digest(provided or "", config.WEBHOOK_SECRET) ``` `App/config.py:11-16`: ```python # Security WEBHOOK_SECRET: str = os.getenv("WEBHOOK_SECRET", "") # Server HOST: str = os.getenv("HOST", "0.0.0.0") PORT: int = int(os.getenv("PORT", 8000)) DEBUG: bool = os.getenv("DEBUG", "false").lower() == "true" ``` `docker-compose.yml:6-10`: ```yaml container_name: webhook-to-telegram ports: - "8000:8000" env_file: - .env ``` ### Technical Analysis Authentication is disabled whenever `WEBHOOK_SECRET` is empty. At the same time, the server defaults to `0.0.0.0`, and the Compose configuration publishes port 8000 on the host without restricting it to loopback. The documented configuration calls the secret optional. Therefore, a normal deployment can expose an unauthenticated Telegram-forwarding endpoint to an external network. Authentication fails open instead of rejecting an insecure production configuration. Constant-time comparison is correctly used when a secret exists, but it does not mitigate the complete absence of authentication when the value is unset. ### Attack Path 1. An operator follows the documented setup but omits the optional `WEBHOOK_SECRET`. 2. The application binds to `0.0.0.0:8000`. 3. Docker publishes the service through host port 8000. 4. A remote attacker discovers or is given the webhook URL. 5. The attacker submits forged alert payloads to `/we ...[truncated 628 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Require a non-empty `WEBHOOK_SECRET` during startup when the server binds to a non-loopback address. - Prefer a fail-closed model in which an absent secret causes startup failure or all webhook requests to be rejected. - Change the default host to `127.0.0.1`. - If direct public exposure is required, place the service behind a TLS-enabled reverse proxy and restrict source networks where possible. - Bind the Compose port to loopback by default: ```yaml ports: - "127.0.0.1:8000:8000" ``` - Use a high-entropy, randomly generated secret and support safe secret rotation. - Clearly document that authentication is mandatory for any non-local deployment. - Add startup validation similar to: ```python if self.HOST not in {"127.0.0.1", "localhost"} and not self.WEBHOOK_SECRET: raise EnvironmentError( "WEBHOOK_SECRET is required when listening on a non-loopback interface" ) ``` ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
App/webhook.py:44
Finding
Sensitive Webhook Payloads Are Logged in Full<![CDATA[ ## Vulnerability Details **File Location**: `App/webhook.py:44` **Vulnerability Type**: Sensitive data exposure through application logs **Risk Level**: Medium ### Vulnerable Code ```python logger.info(f"Received webhook payload: {payload}") ``` ### Technical Analysis The application accepts generic webhook events from TradingView, CI/CD systems, uptime monitors, and custom sources. Such payloads can contain access tokens, internal URLs, deployment metadata, customer data, or other confidential values. The complete deserialized payload is written at the normal `INFO` log level without redaction or an allowlist. Container logs and centralized logging systems may retain this data for long periods and expose it to more users or services than the original webhook content. ### Attack Path 1. A legitimate integration sends a webhook containing sensitive values, such as a deployment token, internal endpoint, or private message. 2. The application deserializes the request body into `payload`. 3. The complete dictionary is interpolated into the log message. 4. The payload is persisted in local container output or a centralized logging platform. 5. A user or service with log-reading access obtains information that was only intended for the Telegram destination. An attacker may also deliberately submit control characters or misleading content to contaminate log records, although the confirmed primary issue is disclosure of request data. ### Impact Assessment The affected privileges depend on the data contained in incoming payloads. Exposure may reveal credentials, internal infrastructure details, operational events, or personal information to log administrators and connected logging systems. This issue does not independently provide remote code execution or host privileges. Its confidentiality impact can become significant if integrations include reusable secrets or sensitive CI/CD metadata. ]]>
Remediation
<![CDATA[ ## Remediation Suggestions - Do not log complete webhook bodies in production. - Log only non-sensitive, allowlisted metadata such as a request identifier, event type, payload size, and processing result. - Redact keys commonly associated with secrets, including `token`, `secret`, `password`, `authorization`, `api_key`, and `cookie`. - Avoid relying solely on key-name redaction because secrets may appear in arbitrary message fields. - Restrict access to application logs and configure suitable retention periods. - Consider enabling detailed payload logging only through a temporary, explicitly controlled diagnostic option. - Use structured logging rather than interpolating the entire Python object. Example: ```python logger.info( "Received webhook", extra={ "event_type": payload.get("action", "generic"), "field_count": len(payload), }, ) ``` ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
App/formatter.py:82
Finding
Telegram Markdown Injection Through Unescaped Webhook Fields<![CDATA[ ## Vulnerability Details **File Location**: `App/formatter.py:82-87`; Markdown rendering is enabled at `App/telegram.py:22-29` **Vulnerability Type**: Output markup injection **Risk Level**: Medium ### Vulnerable Code `App/formatter.py:82-87`: ```python def format_raw(payload: dict[str, Any]) -> str: """Fallback: format any arbitrary payload as a readable key-value list.""" lines = ["🔔 *New Alert*", ""] for key, value in payload.items(): lines.append(f"• *{key}:* `{value}`") lines += ["", "⚡ _Powered by Webhook-to-Telegram_"] return "\n".join(lines) ``` `App/telegram.py:22-29`: ```python payload = { "chat_id": target_chat, "text": text, "parse_mode": "Markdown", "disable_web_page_preview": True, } ``` The signal formatter similarly interpolates untrusted values into Markdown constructs, including fields at `App/formatter.py:45-75`. ### Technical Analysis Webhook keys and values are directly inserted into Telegram Markdown without escaping reserved delimiters. Because Telegram is explicitly instructed to parse the message as Markdown, an attacker can inject backticks, emphasis markers, and link syntax to terminate the intended formatting and influence the rendered output. This permits an attacker to make a forged webhook message resemble a trusted system message, hide contextual distinctions, or insert attacker-selected links. Some malformed payloads may also cause Telegram to reject the message, resulting in a denial of forwarding. The risk is amplified by the unauthenticated `/webhook/raw` endpoint, but malformed or compromised authenticated webhook sources can exploit the same formatting weakness. ### Attack Path 1. An attacker submits a webhook containing Telegram Markdown delimiters in a key or value. 2. `format_raw` or `format_signal` inserts the field directly into a Markdown-formatted string. 3. `send_message` specifies `"parse_mode": "Markdown"`. 4. Tele ...[truncated 672 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Prefer sending untrusted content as plain text by removing `parse_mode` where rich formatting is not essential. - If Markdown must be retained, escape every caller-controlled key and value using escaping rules compatible with the selected Telegram parse mode. - Consider Telegram MarkdownV2 with a tested escaping function, or construct message entities separately rather than concatenating markup. - Validate field types and reject nested or unsupported structures. - Add tests using backticks, brackets, parentheses, underscores, asterisks, backslashes, and newline characters. - Keep trusted template markup separate from untrusted payload text. Conceptual example: ```python def escape_markdown(value: Any) -> str: text = str(value) for char in r"_*[]()~`>#+-=|{}.!": text = text.replace(char, "\\" + char) return text ``` The escaping implementation must exactly match the Telegram parse mode selected by the application. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
App/webhook.py:22
Finding
Unbounded Webhook Processing and Outbound Message Forwarding<![CDATA[ ## Vulnerability Details **File Location**: `App/webhook.py:22-60` and `App/webhook.py:64-74` **Vulnerability Type**: Missing request-size, message-size, and rate controls **Risk Level**: Medium ### Vulnerable Code ```python @router.post("/webhook") async def receive_webhook( request: Request, x_webhook_secret: str = Header(default=None), ): """ Main webhook endpoint. Accepts JSON payload from TradingView or any custom source. Validates optional secret, formats the signal, and sends to Telegram. """ # --- Auth check --- if not verify_secret(x_webhook_secret): logger.warning("Unauthorized webhook attempt") raise HTTPException(status_code=401, detail="Invalid webhook secret") # --- Parse body --- try: payload: dict[str, Any] = await request.json() except Exception: raise HTTPException(status_code=400, detail="Invalid JSON payload") logger.info(f"Received webhook payload: {payload}") # --- Format & send --- try: # Use signal formatter if payload looks like a trading alert if any(k in payload for k in ("action", "signal", "symbol", "ticker")): message = format_signal(payload) else: message = format_raw(payload) await send_message(message) return {"status": "ok", "message": "Signal forwarded to Telegram"} except Exception as e: logger.error(f"Failed to forward signal: {e}") raise HTTPException(status_code=500, detail="Failed to send Telegram message") ``` ```python @router.post("/webhook/raw") async def receive_raw_webhook(request: Request): """ Raw webhook endpoint — always uses key-value formatter. Useful for non-trading alerts (uptime monitors, CI/CD, etc.) """ try: payload: dict[str, Any] = await request.json() except Exception: raise HTTPException(status_code=400, detail="Invalid JSON payload") message = format_raw(payload) ...[truncated 1920 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Enforce a maximum request-body size at the reverse proxy and application layers. - Define Pydantic request models with strict field types and maximum lengths instead of directly calling `request.json()`. - Reject non-object JSON payloads and limit the number of accepted keys. - Cap the final Telegram message length before making an outbound request. - Apply per-IP, per-secret, and global rate limits. - Bound concurrent outbound Telegram requests and use connection pooling rather than creating a new client for each message. - Add backpressure and a bounded queue if asynchronous delivery is introduced. - Return `413 Payload Too Large` for oversized bodies and `429 Too Many Requests` when limits are exceeded. - Avoid logging full rejected bodies. - Configure upstream timeouts and body limits, for example in Nginx: ```nginx client_max_body_size 16k; limit_req zone=webhooks burst=10 nodelay; ``` ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (16)

Credential Access

High
Category
Privilege Escalation
Content
### 2. Configure Environment

```bash
cp .env.example .env
```

Edit `.env` and fill in your values:
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The declared purpose sounds like a narrow TradingView-to-Telegram relay, but the documentation describes a broader general-purpose webhook forwarder, including arbitrary custom payloads and a raw endpoint. This mismatch can mislead reviewers and users about the trust boundary, making it easier to deploy an internet-exposed message relay that can be abused for spam, alert injection, or exfiltration through Telegram.

Credential Access

High
Category
Privilege Escalation
Content
### Step 3 — Configure Environment

```bash
cp .env.example .env
```

Edit `.env`:
Confidence
87% confidence
Finding
The skill instructs users to place the Telegram bot token into a .env file, which is common practice, but the documentation does not warn about protecting that file from source control, sharing, or accidental exposure. Because the token grants control of the bot, leakage can let an attacker send messages, abuse the bot, or hijack notifications.

Credential Access

High
Category
Privilege Escalation
Content
ports:
      - "8000:8000"
    env_file:
      - .env
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

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.

External Transmission

Medium
Category
Data Exfiltration
Content
logger = logging.getLogger(__name__)

TELEGRAM_API = "https://api.telegram.org/bot{token}/{method}"


async def send_message(text: str, chat_id: str = None) -> dict:
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The function sends data to the Telegram API, including a photo URL, caption, and chat identifier, but unlike `send_message` it has no docstring warning, comment, or logging indicating that external transmission occurs. For code files, outbound network calls that transmit user or system data should have some visible disclosure unless clearly documented as expected behavior.

Description-Behavior Mismatch

Medium
Confidence
95% confidence
Finding
The manifest describes a narrowly scoped bridge for TradingView alerts to Telegram. However, the main endpoint explicitly accepts payloads from "TradingView or any custom source," and the separate raw endpoint is documented for non-trading alerts such as uptime monitors and CI/CD, expanding the skill beyond the stated trading-alert purpose.

Description-Behavior Mismatch

Medium
Confidence
97% confidence
Finding
The /webhook/raw route always formats and forwards arbitrary JSON as key-value text, and its docstring explicitly targets non-trading alerts. This is broader than "Forward trading alerts and webhook events from TradingView to Telegram instantly" and represents a meaningful behavior mismatch at the skill level.

Missing User Warnings

Medium
Confidence
98% confidence
Finding
The /webhook/raw endpoint accepts arbitrary JSON and forwards it to Telegram without any authentication, unlike the main /webhook route which verifies a secret. This allows any external party who can reach the service to send unsolicited messages to the configured Telegram destination, enabling spam, alert forgery, phishing-style content delivery, and operational disruption.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The README explicitly promotes forwarding arbitrary webhook payloads to Telegram but does not clearly warn users that submitted data will be transmitted to a third-party messaging platform. In this context, operators may send monitoring, CI/CD, or trading data that could contain sensitive information, creating a confidentiality risk through unreviewed external sharing.

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill requires environment variables and clearly exposes network-facing behavior, but it does not declare an explicit tool scope such as permissions or allowed-tools. That weakens least-privilege controls and can cause an agent or operator to invoke the skill without realizing it needs secret access and network transmission capabilities.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The instructions tell users to provide a Telegram bot token and chat ID, but do not include an explicit warning that the bot token is a sensitive secret that must not be pasted into chats, logs, or shared prompts. In an agent-assisted workflow, this increases the chance of credential disclosure to unintended parties or storage in unsafe locations.

External Transmission

Medium
Category
Data Exfiltration
Content
Send a test webhook with curl:

```bash
curl -X POST http://localhost:8000/webhook \
  -H "Content-Type: application/json" \
  -H "X-Webhook-Secret: your_secret_here" \
  -d '{
Confidence
60% 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
Send a test webhook with curl:

```bash
curl -X POST http://localhost:8000/webhook \
  -H "Content-Type: application/json" \
  -H "X-Webhook-Secret: your_secret_here" \
  -d '{
Confidence
60% 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: python-dotenv==1.0.1 — 2 advisory(ies): CVE-2026-28684 (python-dotenv: Symlink following in set_key allows arbitrary file overwrite via ); CVE-2026-28684 (python-dotenv reads key-value pairs from a .env file and can set them as environ)

Medium
Category
Supply Chain
Confidence
96% confidence
Finding
The dependency is pinned to a version reported as vulnerable to unsafe symlink handling in set_key, which can enable arbitrary file overwrite in workflows that modify .env files. In a webhook-forwarding service that may run with filesystem access and secrets in environment files, this increases risk if any code path or operator tooling uses python-dotenv write functionality on attacker-influenced paths.

Static analysis

No suspicious patterns detected.