Back to skill

Security audit

Telegram PDF Scraper

Security checks for vulnerabilities and agentic risk

Overview

This Telegram PDF downloader matches its general purpose, but it overstates its safety and can save unverified channel-controlled downloads outside the intended folder while persisting a Telegram browser profile.

Install only if you are comfortable using a Telegram Web session for automation and saving files from the selected channel. Use a dedicated non-sensitive download folder, treat all downloaded PDFs as untrusted, and prefer a revised version that validates Telegram attachment origins, confirms downloads, enforces PDF/type and size checks, blocks path traversal, and clearly documents or isolates the persistent browser profile.

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

T09 · Insecure Skill Coding Practices

Error
Location
main.py:88
Finding
Channel-Controlled Directory Traversal in Download Destination<![CDATA[ ## Vulnerability Details **File Location**: `main.py`, lines 88–95 and 127–132 **Vulnerability Type**: Directory traversal and insufficient destination-path validation **Risk Level**: High ### Vulnerable Code ```python # Extract the text of the message to use as a header msg_text = msg.inner_text() # --- HEADER DETECTION --- # If message has text, split by newlines. # We assume the first line (if it doesn't contain 'http' or 'publicearn') is the category header. if msg_text: lines = [line.strip() for line in msg_text.split('\n') if line.strip()] if lines: first_line = lines[0] # Heuristic: If it looks like a title (no URLs, reasonable length) if "http" not in first_line and "publicearn" not in first_line and len(first_line) < 60: sanitized_header = sanitize_name(first_line) if sanitized_header: current_folder = sanitized_header ``` ```python # Create target directory based on the current header target_dir = os.path.join(base_dir, current_folder) if not os.path.exists(target_dir): os.makedirs(target_dir) file_path = os.path.join(target_dir, safe_filename) ``` The relevant sanitizer is: ```python def sanitize_name(text): """Removes emojis, illegal OS characters, and trims whitespace for safe folder/file names.""" # Remove emojis and special characters, keep alphanumeric, spaces, and basic punctuation clean_text = re.sub(r'[^\w\s\-\.]', '', text) return re.sub(r'[\\/*?:"<>|]', "", clean_text).strip() ``` ### Technical Analysis Folder names are derived from Telegram message content, which can be controlled by a channel administrator or anyone otherwise able to publish messages in the selected channel. Although `sanitize_name()` removes path separators, it explicitly permits periods and does not reject the special path components `.` and `..`. Consequently, a message whose first nonempty line is `..` sets `current_folder` to `..`. The expression `os.path.jo ...[truncated 2304 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Explicitly reject empty names, `.` and `..` after sanitization. 2. Resolve the base directory and destination using `os.path.realpath()` or `pathlib.Path.resolve()`. 3. Verify containment before creating a directory or saving a file: ```python from pathlib import Path base_path = Path(download_directory).resolve() def safe_child(base: Path, folder: str, filename: str) -> Path: if folder in {"", ".", ".."}: raise ValueError("Invalid folder name") destination = (base / folder / filename).resolve() if not destination.is_relative_to(base): raise ValueError("Destination escapes the configured download directory") return destination ``` 4. Reject absolute paths and all path separators before path construction, even if the current sanitizer is expected to remove them. 5. Refuse to follow symbolic links in destination components. Where supported, use descriptor-based filesystem operations and no-follow flags to reduce time-of-check/time-of-use risks. 6. Use `mkdir(parents=True, exist_ok=True)` only after containment validation. 7. Avoid silently replacing existing files. Use exclusive file creation or generate a collision-safe filename. 8. Add tests for `..`, `.`, Unicode path edge cases, symbolic links, absolute paths, and nested traversal attempts. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
main.py:105
Finding
Unverified Downloads Are Renamed and Stored as PDF Files<![CDATA[ ## Vulnerability Details **File Location**: `main.py`, lines 105–148 **Vulnerability Type**: Insufficient file-type and download-origin validation **Risk Level**: Medium ### Vulnerable Code ```python links = msg.locator("a").all() for link in links: try: href = link.get_attribute("href") or "" text = link.inner_text() link_id = f"{href}_{text}" # Unique identifier for this session if not text or link_id in processed_links: continue processed_links.add(link_id) # THE SAFETY FILTER (CRITICAL) malicious_domains = ["publicearn.in", "bit.ly", "http://", "https://"] if any(domain in href for domain in malicious_domains): print(f" [BLOCKED] Ignored dangerous external link: {text}") continue # If it passes the filter, it's an internal file/link safe_filename = sanitize_name(text) if not safe_filename: continue # Ensure it ends with .pdf if not safe_filename.lower().endswith('.pdf'): safe_filename += ".pdf" # Create target directory based on the current header target_dir = os.path.join(base_dir, current_folder) if not os.path.exists(target_dir): os.makedirs(target_dir) file_path = os.path.join(target_dir, safe_filename) # Check if already downloaded on disk if os.path.exists(file_path): print(f" [SKIPPED] File already exists: {safe_filename} (in {current_folder})") continue # Execute Download Trigger print(f" [DOWNLOADING] Found internal file: {safe_filename} -> to folder '{current_folder}'") # We expect a download event when clicking the internal Telegram link with page.expect_download(timeout=15000) as download_info: # Force click, bypassing any overlay link.click(force=True) download = download_info.value # Save the file ...[truncated 2693 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Use selectors specific to Telegram document attachments instead of iterating over every `<a>` element in a message. 2. Require Telegram's displayed document metadata and original filename to identify the object as a PDF. 3. Download to a temporary file first rather than directly to the final destination. 4. Verify the file signature before accepting it: ```python with open(temp_path, "rb") as downloaded_file: if downloaded_file.read(5) != b"%PDF-": os.remove(temp_path) raise ValueError("Downloaded content is not a PDF") ``` 5. Validate the MIME type where reliable, but do not rely on MIME type alone. 6. Optionally parse the file with a maintained PDF parser in a sandbox to confirm structural validity. 7. Impose explicit per-file and total-download size limits. 8. Delete temporary files whenever validation fails. 9. Validate the final download URL or origin against an allowlist of expected Telegram-controlled origins. 10. Do not describe the implementation as “PDF-only” or “native document only” until those properties are positively enforced. ]]>

T08 · Insecure Dependencies

Note
Location
requirements.txt:1
Finding
Unbounded Playwright Dependency Produces Non-Reproducible Installations<![CDATA[ ## Vulnerability Details **File Location**: `requirements.txt`, line 1 **Vulnerability Type**: Unbounded third-party dependency version **Risk Level**: Low ### Vulnerable Code ```text playwright>=1.40.0 ``` ### Technical Analysis The dependency declaration specifies only a minimum version. Package installation may therefore select any later Playwright release available from the configured package index. This does not demonstrate that the current dependency is malicious. However, it prevents reproducible dependency resolution and allows future, unreviewed releases to enter the execution environment without a corresponding source-code change in this project. Playwright also controls browser automation and browser-binary interactions, making changes to this dependency security-relevant. No package hashes are provided, so installation does not verify artifacts against an audit-approved lock file. ### Attack Path 1. The project is installed or rebuilt at a later date. 2. The package resolver selects a Playwright release newer than the version originally reviewed. 3. The new package is downloaded from the configured package source without hash verification. 4. Installation or runtime loads dependency code that was not covered by this audit. 5. If the selected release or package source is compromised, that code executes with the installer or skill process's privileges. This is a supply-chain exposure rather than evidence of an existing malicious Playwright release. ### Impact Assessment A compromised or unexpectedly incompatible future dependency could execute with the privileges of the Python environment running the skill. In the worst case, that scope could include access to: - Files accessible to the current user. - The persistent browser-profile directory. - Telegram Web session state available to the automated browser. - Network resources available to the process. - Downloaded study-material directories. The practical likelihood depends on t ...[truncated 159 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin Playwright to an exact, reviewed version: ```text playwright==<reviewed-version> ``` 2. Generate a lock file containing transitive dependencies and cryptographic hashes, for example with `pip-tools`. 3. Install dependencies with hash enforcement: ```bash pip install --require-hashes -r requirements.txt ``` 4. Use a trusted, explicitly configured package index. 5. Review and test dependency updates before changing the lock file. 6. Add automated dependency-vulnerability and integrity scanning to the release process. 7. Pin and manage the corresponding Playwright browser binaries so package and browser versions remain compatible and reproducible. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • 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 (7)

Intent-Code Divergence

High
Confidence
98% confidence
Finding
The code advertises a safety filter for dangerous external links, but the filter only blocks a few hard-coded substrings and mistakenly treats any other href as safe. An attacker controlling Telegram channel content can supply a non-blocklisted external URL or a crafted link that still triggers a file download, causing the skill to fetch untrusted content and save it locally under the user's authenticated browser session.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The skill explicitly downloads files from Telegram and creates local folders, but it does not clearly warn the user about local file system modifications and acquisition of untrusted content from an external channel. Even if it claims to restrict downloads to Telegram document objects and PDFs, PDFs can still be malicious or unwanted, and silent local file creation increases the risk of unsafe execution or cluttering sensitive directories.

Missing User Warnings

Medium
Confidence
83% confidence
Finding
The code stores Telegram authentication state in a persistent local Chrome profile without prominently informing the user that their session tokens and browsing state will remain on disk. If the local machine or working directory is accessible to other users or processes, those persisted credentials may be reused to access the user's Telegram account.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The skill automatically downloads files referenced in channel messages and writes them to disk with no clear user-facing warning or consent at the point of download. In this context, Telegram channel content is untrusted input, so silent saving of attacker-controlled files can expose users to malicious documents, storage abuse, and unsafe handling of downloaded content.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The manifest advertises scraping Telegram channels through browser automation without disclosing the privacy-sensitive network activity involved. This can expose account context, channel membership, browsing/session data, and downloaded content handling in ways a user may not expect, especially when automation is performed against a personal Telegram session.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The manifest advertises scraping Telegram channels through browser automation without disclosing the privacy-sensitive network activity involved. This can expose account context, channel membership, browsing/session data, and downloaded content handling in ways a user may not expect, especially when automation is performed against a personal Telegram session.

Unpinned Dependencies

Low
Category
Supply Chain
Content
playwright>=1.40.0
Confidence
95% confidence
Finding
The dependency is specified with a lower-bound version constraint only, which allows future unreviewed versions of Playwright to be installed. This can introduce supply-chain risk, build instability, or unexpected vulnerable/breaking versions over time, especially in automated environments where installs happen without manual review.

Static analysis

No suspicious patterns detected.