Back to skill

Security audit

tg-cli

Security checks for vulnerabilities and agentic risk

Overview

This Telegram CLI skill is coherent, but it gives agents broad access to a personal Telegram account, locally caches private chats, and can send real messages.

Install only if you are comfortable giving this tool access to your personal Telegram account. Use a dedicated account or narrowly scoped chats where possible, prefer structured YAML/JSON output, protect TG_API_ID/TG_API_HASH and the Telethon session like credentials, keep the data directory private, avoid scheduler/listen persistence unless you need it, and manually confirm any tg send command before allowing an agent to run it.

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
src/tg_cli/config.py:83
Finding
Sensitive Telegram session and message database files lack enforced restrictive permissions<![CDATA[ ## Vulnerability Details **File Locations**: - `src/tg_cli/config.py:83-108` - `src/tg_cli/client.py:37-50` - `src/tg_cli/db.py:55-66` **Vulnerability Type**: Insecure permissions for sensitive local data **Risk Level**: High ### Vulnerable Code ```python def get_session_path() -> str: """Return session file path inside data/ directory.""" data_dir = get_data_dir() name = get_session_name() return str(data_dir / name) def get_data_dir() -> Path: """Return data directory, create if not exists.""" raw = os.environ.get("DATA_DIR", "") if raw: d = _resolve_env_path(raw) else: d = _default_data_home() / APP_NAME d.mkdir(parents=True, exist_ok=True) return d def get_db_path() -> Path: raw = os.environ.get("DB_PATH", "") if raw: p = _resolve_env_path(raw) else: p = get_data_dir() / "messages.db" p.parent.mkdir(parents=True, exist_ok=True) return p ``` ```python @asynccontextmanager async def connect() -> AsyncGenerator[TelegramClient, None]: """Async context manager for Telegram client — single connection, reuse within scope.""" try: api_id = get_api_id() api_hash = get_api_hash() except MissingTelegramCredentialsError as exc: raise click.ClickException(str(exc)) from exc c = TelegramClient(get_session_path(), api_id, api_hash) await c.start() try: yield c finally: await c.disconnect() ``` ```python class MessageDB: """SQLite message store with context manager support.""" def __init__(self, db_path: Path | str | None = None): if db_path is None: self.db_path = get_db_path() else: self.db_path = Path(db_path) self.db_path.parent.mkdir(parents=True, exist_ok=True) self.conn = sqlite3.connect(str(self.db_path)) self.conn.row_factory = sqlite3.Row self.conn.execute("PRAGMA journal_mode=WAL") self.conn.execute ...[truncated 2352 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Create the application data directory with owner-only permissions: ```python d.mkdir(parents=True, exist_ok=True, mode=0o700) d.chmod(0o700) ``` 2. Verify that configured data paths are owned by the current user and are not group-writable or world-writable. Refuse to use unsafe locations unless the user explicitly overrides a warning. 3. After creating or opening sensitive files, enforce mode `0600` on: - The Telethon session file. - `messages.db`. - SQLite `messages.db-wal` and `messages.db-shm` files when present. 4. Temporarily use a restrictive umask such as `0o077` while creating sensitive application files. 5. Detect and reject symbolic links for session and database targets where practical, reducing the risk of writing to or opening an unintended file. 6. Validate `TG_SESSION_NAME` as a simple filename rather than allowing path separators or traversal components. 7. Document that the database contains plaintext message history and that the Telethon session must be protected like an authentication credential. 8. Add automated tests that run under a permissive umask and verify that all created directories and sensitive files remain accessible only to their owner. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
src/tg_cli/client.py:259
Finding
Attacker-controlled Telegram content is rendered as Rich markup<![CDATA[ ## Vulnerability Details **File Locations**: - `src/tg_cli/client.py:259-292` - `src/tg_cli/cli/query.py:103-110` - `src/tg_cli/cli/query.py:389-400` - `src/tg_cli/cli/query.py:490` **Vulnerability Type**: Terminal output and Rich-markup injection **Risk Level**: Medium ### Vulnerable Code The real-time listener interpolates Telegram-controlled chat names, sender names, and message content into a Rich markup string: ```python me = await client.get_me() console.print(f"[green]✓[/green] Logged in as [bold]{me.first_name}[/bold] ({me.phone})") console.print("[dim]Listening for messages... Press Ctrl+C to stop.[/dim]") @client.on(events.NewMessage(chats=chats)) async def handler(event): msg = event.message chat = await event.get_chat() sender = await event.get_sender() chat_name = ( getattr(chat, "title", None) or getattr(chat, "first_name", None) or "Unknown" ) sender_name = _get_sender_name(sender) content = msg.text or msg.message or "" ts = msg.date if ts and ts.tzinfo is None: ts = ts.replace(tzinfo=timezone.utc) db.insert_message( chat_id=chat.id, chat_name=chat_name, msg_id=msg.id, sender_id=msg.sender_id, sender_name=sender_name, content=content, timestamp=ts or datetime.now(timezone.utc), ) time_str = ts.strftime("%H:%M:%S") if ts else "??:??:??" console.print( f"[dim]{time_str}[/dim] [cyan]{chat_name}[/cyan] | " f"[bold]{sender_name or 'Unknown'}[/bold]: {content[:200]}" ) ``` Search output follows the same pattern: ```python for msg in results: ts = (msg.get("timestamp") or "")[:19] sender = msg.get("sender_name") or "Unknown" chat_name = msg.get("chat_name") or "" content = (msg.get("content") or "")[:200] console.print( f"[dim]{ts}[/dim] [cyan]{chat_name}[/cyan] | " f"[bold]{sender}[/bold]: {content}" ) ``` Today's message display also renders untrust ...[truncated 3145 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Escape every Telegram-derived value before inserting it into a Rich markup string: ```python from rich.markup import escape safe_chat = escape(chat_name) safe_sender = escape(sender_name or "Unknown") safe_content = escape(content[:200]) console.print( f"[dim]{time_str}[/dim] [cyan]{safe_chat}[/cyan] | " f"[bold]{safe_sender}[/bold]: {safe_content}" ) ``` 2. Prefer `Text` objects so styles are applied only to trusted components and external values remain literal. 3. For lines containing entirely untrusted content, explicitly disable markup: ```python console.print(content, markup=False, highlight=False) ``` 4. Remove the explicit `markup=True` setting for cached message content. 5. Escape untrusted chat names, sender names, usernames, profile names, error text, and message bodies consistently across all output paths. 6. Consider sanitizing terminal control characters in addition to Rich markup, especially ESC, C0 control characters, and bidirectional text controls. 7. Retain JSON/YAML structured output as the preferred interface for AI Agents and automated consumers. 8. Add regression tests using values such as `[bold]forged[/bold]`, `[/cyan]`, hyperlink tags, escape characters, and bidirectional controls. Verify that output displays those values literally. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (33)

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The declared description understates the skill's behavior by omitting outbound message sending, account inspection, and chat metadata access. This can mislead users or policy engines into authorizing a skill for passive monitoring when it also supports active actions and sensitive account/context enumeration.

Credential Access

High
Category
Privilege Escalation
Content
```bash
export TG_API_ID=123456
export TG_API_HASH=your_telegram_app_hash
# Or create a .env file with the same variables
tg chats              # First run: enter phone + verification code
tg whoami             # Check current user
```
Confidence
82% confidence
Finding
The skill instructs users to place Telegram API credentials in environment variables or a `.env` file, which is normal operationally but sensitive in agent environments with file and env access. If mishandled, these secrets can be exposed through logs, workspace leakage, shell history, or overly broad file access, enabling Telegram account/API abuse.

Context Leakage

High
Category
Data Exfiltration
Content
tg listen                         # Real-time listener
tg listen --persist               # Reconnect automatically for a near-live cache
tg info CHAT                      # Chat details
tg send CHAT "Hello!"             # Send a message
```

### Search & Query
Confidence
85% confidence
Finding
Code or instructions that leak agent conversation context to external services, potentially exposing sensitive user interactions.

Credential Access

High
Category
Privilege Escalation
Content
"""Configuration management - loads from .env or environment variables."""

from __future__ import annotations
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
"""Configuration management - loads from .env or environment variables."""

from __future__ import annotations
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
"""Configuration management - loads from .env or environment variables."""

from __future__ import annotations
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
"""Configuration management - loads from .env or environment variables."""

from __future__ import annotations
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
def _load_env() -> None:
    """Load .env from cwd first, then fall back to the source checkout."""
    for candidate in (Path.cwd() / ".env", _PROJECT_ROOT / ".env"):
        if candidate.is_file():
            load_dotenv(candidate)
            return
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The README explicitly promotes syncing Telegram chats into a local SQLite cache and exporting messages, but it does not warn that private conversations, metadata, and potentially sensitive content will be stored unencrypted or retained locally. In an agent-skill context, this increases the chance that operators or downstream automation will treat the cache as harmless and accidentally expose personal or confidential data through local compromise, backups, logs, or further export.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The README shows a `tg send` example but does not warn that the command sends messages using the user's own Telegram account rather than a limited bot identity. In an agent-integrated CLI, omission of that warning can cause accidental outbound messaging, impersonation of the user, or unreviewed transmission of sensitive content if an automation layer invokes the command.

Session Persistence

Medium
Category
Rogue Agent
Content
Typical flow:

```bash
mkdir -p ~/.config/systemd/user
cp examples/systemd/tg-refresh.service ~/.config/systemd/user/
cp examples/systemd/tg-refresh.timer ~/.config/systemd/user/
systemctl --user daemon-reload
Confidence
60% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Session Persistence

Medium
Category
Rogue Agent
Content
Typical flow:

```bash
mkdir -p ~/.config/systemd/user
cp examples/systemd/tg-refresh.service ~/.config/systemd/user/
cp examples/systemd/tg-refresh.timer ~/.config/systemd/user/
systemctl --user daemon-reload
Confidence
60% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Session Persistence

Medium
Category
Rogue Agent
Content
cp examples/systemd/tg-refresh.service ~/.config/systemd/user/
cp examples/systemd/tg-refresh.timer ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now tg-refresh.timer
```

## Use as AI Agent Skill
Confidence
80% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Session Persistence

Medium
Category
Rogue Agent
Content
cp examples/systemd/tg-refresh.service ~/.config/systemd/user/
cp examples/systemd/tg-refresh.timer ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now tg-refresh.timer
```

## Use as AI Agent Skill
Confidence
80% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Lp3

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding
The skill exposes capabilities involving environment variables and local file access, but it does not declare any explicit tool scope or permission boundaries. In an agent setting, this increases the chance that a caller or orchestrator grants broader access than users expect, especially because the skill handles Telegram credentials and local cached message data.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The skill documents a `send` capability without a strong warning that it performs outbound actions on a real Telegram account and can affect external chats. In agent-driven workflows, this creates a risk of unintended messaging, spam, impersonation, or disclosure to the wrong recipients.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The documented `purge CHAT -y` command deletes stored local messages, but the skill does not clearly warn about irreversible local data loss. Users or agents may run it casually during cleanup or troubleshooting and destroy cached evidence or analysis history.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
## Safety Notes

- Do not ask users to share phone numbers or verification codes in chat logs.
- Session data is stored locally and never uploaded.
Confidence
80% confidence
Finding
Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
@data_group.command("purge")
@click.argument("chat")
@click.option("-y", "--yes", is_flag=True, help="Skip confirmation")
def purge(chat: str, yes: bool):
    """Delete all stored messages for CHAT."""
    with MessageDB() as db:
Confidence
85% confidence
Finding
Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The helper normalizes and returns full Telegram profile data including phone number without masking or any indication that sensitive data will be emitted. Because this data may be included in JSON/YAML output and agent pipelines, it creates avoidable privacy leakage and persistence in logs or transcripts.

Description-Behavior Mismatch

Medium
Confidence
94% confidence
Finding
The skill metadata describes sync/search/filter/monitor capabilities, but this file also exposes a send command that can transmit outbound Telegram messages. That scope expansion is security-relevant because an agent or user may invoke the skill assuming it is read-only, enabling unintended external actions, spam, impersonation, or data leakage through sent content.

Missing User Warnings

Medium
Confidence
87% confidence
Finding
The function retrieves historical message content and writes it to a local database via insert_batch, which affects user data and privacy. While there are docstrings describing the behavior, there is no visible user-facing disclosure, confirmation, or warning in this file before bulk message storage occurs.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The listener captures new messages and persists chat IDs, sender information, and message content to the database using insert_message. Although the console announces that it is listening, it does not explicitly warn that messages are being saved, which is a privacy-relevant operation.

Description-Behavior Mismatch

Medium
Confidence
92% confidence
Finding
The skill description frames the tool as a terminal interface for syncing chats, searching messages, filtering keywords, and monitoring groups. The delete_chat method implements destructive deletion of all stored messages for a chat, which is outside that read/monitor/search-oriented description.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The delete_chat method permanently deletes all messages for a chat and commits the change immediately. Although the docstring states what the method does, there is no confirmation prompt, user-facing log/output, or broader warning in this file about the irreversible data deletion.

Static analysis

No suspicious patterns detected.