Back to skill

Security audit

X Reader

Security checks for vulnerabilities and agentic risk

Overview

This appears to be a real URL reader, but it needs Review because arbitrary URL fetching, saved login sessions, and incomplete network protections could expose private or authenticated content.

Install only if you trust the publisher and are comfortable with the tool fetching arbitrary URLs from your machine, saving fetched content locally, and optionally storing browser login sessions. Avoid using it on untrusted links in cloud or corporate environments until SSRF protections are hardened, and do not enable saved sessions or external MCP exposure unless you understand the account and network exposure.

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

T09 · Insecure Skill Coding Practices

Error
Location
utils/url_validator.py:79
Finding

SSRF Protection Can Be Bypassed Through Redirects and DNS Rebinding

Content
View full analysis

Vulnerability Details

File Location: utils/url_validator.py:79-89; affected network sinks include fetchers/browser.py:71, fetchers/twitter.py:132, fetchers/rss.py:25, and fetchers/youtube.py:53,121
Vulnerability Type: Server-Side Request Forgery through time-of-check/time-of-use DNS resolution and unvalidated redirects
Risk Level: High

Vulnerable Code

utils/url_validator.py:79-89 validates DNS results only once and then returns the original URL:

python
try:
    resolved = socket.getaddrinfo(hostname, None, socket.AF_UNSPEC, socket.SOCK_STREAM)
except socket.gaierror:
    raise ValueError(f"Blocked: cannot resolve hostname '{hostname}'")

for family, _, _, _, sockaddr in resolved:
    ip = ipaddress.ip_address(sockaddr[0])
    for network in _BLOCKED_NETWORKS:
        if ip in network:
            raise ValueError(
                f"Blocked: '{hostname}' resolves to private address {ip}"
            )

return url

fetchers/browser.py:71 subsequently performs a separate browser navigation that may resolve the hostname again and follow redirects:

python
await page.goto(url, wait_until="domcontentloaded", timeout=TIMEOUT_MS)

The Twitter-specific browser implementation has the same behavior at fetchers/twitter.py:132:

python
await page.goto(url, wait_until="domcontentloaded", timeout=30_000)

fetchers/rss.py:25 delegates URL retrieval and redirect handling to feedparser:

python
feed = feedparser.parse(url, resolve_relative_uris=False)

The YouTube fetcher passes the validated URL to yt-dlp, which performs its own subsequent resolution and network requests. The subtitle path at fetchers/youtube.py:40-54 is:

python
cmd = [
    "yt-dlp",
    "--write-auto-sub",
    "--write-sub",
    "--sub-lang", lang,
    "--sub-format", "srt",
    "--skip-download",
    "-o", output_path,
    url,
]

try:
    subpr
...[truncated 3500 chars]
Remediation
View remediation

Remediation Suggestions

  1. Disable automatic redirects for HTTP clients. Process redirects manually and run the complete scheme, hostname, port, and resolved-address validation on every Location target.
  2. Prevent DNS time-of-check/time-of-use differences by connecting to a validated, pinned IP address while preserving the intended TLS hostname and HTTP Host value. Alternatively, verify the actual connected peer address before accepting response data.
  3. Reject all non-global address classes rather than relying on a partial network list. Cover private, loopback, link-local, multicast, unspecified, reserved, carrier-grade NAT, documentation, and IPv4-mapped IPv6 addresses.
  4. For Playwright, register request-routing handlers before navigation and abort top-level, redirect, iframe, script, image, media, WebSocket, and other subresource requests whose resolved destinations are not allowed.
  5. Replace direct feedparser.parse(url) retrieval with a hardened HTTP client that implements the redirect and peer-IP policy. Parse the resulting response bytes locally.
  6. Apply equivalent network restrictions to yt-dlp, preferably by isolating it in a sandbox or network namespace that cannot access loopback, link-local, metadata, or private networks.
  7. Revalidate the final browser URL after navigation, but do not rely on final-URL validation alone because the sensitive request may already have occurred.
  8. Add regression tests for public-to-private redirects, redirect chains, DNS rebinding, mixed IPv4/IPv6 results, decimal or encoded IP representations, and browser subresource requests.
  9. Keep the MCP SSE transport bound to localhost by default. If external exposure is required, add authentication, authorization, rate limiting, and network-level egress controls.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Output HandlingUnvalidated Output Injection, Cross-Context Output, Unbounded Output
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
Findings (35)

Tainted flow: 'api_key' from os.getenv (line 98, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · fetchers/youtube.py (reported line 150)May include surrounding context.

python
import requests
        try:
            with open(audio_path, "rb") as f:
                response = requests.post(
                    "https://api.groq.com/openai/v1/audio/transcriptions",
                    headers={"Authorization": f"Bearer {api_key}"},
                    files={"file": (os.path.basename(audio_path), f, "audio/mp4")},

YARA rule 'agent_skill_remote_bootstrap_execution': Remote script or code download followed by execution/bootstrap installation [agent_skills]

High
Category
YARA Match
Confidence
85% confidence
Finding

YARA rule matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).

Content

Scanner excerpt · README.md (reported line 124)May include surrounding context.

) | — | ✅ via Claude Code skill | | Apple Podcasts | — | ✅ via Claude Code skill | | Any web page | ✅ Jina fallback | — |

*XHS requires a one-time login: x-reader login xhs (saves session for Playwright fallback)

YouTube Whisper transcription requires GROQ_API_KEY — get a free key from Groq

Install

bash
# From GitHub (recommended)
pip install git+https://github.com/runesleo/x-reader.git

# With Telegram support
pip install "x-reader[telegram] @ git+https://github.com/runesleo/x-reader.git"

# With browser fallback (Playwright — for XHS/WeChat anti-scraping)
pip install "x-reader[browser] @ git+https://github.com/runesleo/x-reader.git"
playwright install chromium

# With all optional dependencies
pip install "x-reader[all] @ git+https://github.com/runesleo/x-reader.git"
playwright install chromium

Or clone and install locally:

bash
git clone https://github.com/runesleo/x-reader.git
cd x-reader
pip install -e ".

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · README.md (reported line 182)May include surrounding context.

Copy .env.example to .env:

bash
cp .env.example .env

| Variable | Required | Description |

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · fetchers/telegram.py (reported line 6)May include surrounding context.

python
Telegram channel fetcher — uses Telethon.

Requires: pip install x-reader[telegram]
Requires: TG_API_ID + TG_API_HASH in .env
"""

import os

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · fetchers/telegram.py (reported line 46)May include surrounding context.

python
api_hash = os.getenv("TG_API_HASH", "")

    if not api_id or not api_hash:
        raise ValueError("TG_API_ID and TG_API_HASH must be set in .env")

    session = session_path or os.getenv("TG_SESSION_PATH", "./tg_session")

Cloud Metadata Access

High
Category
Server-Side Request Forgery
Confidence
90% confidence
Finding

Code accesses a cloud instance metadata endpoint (e.g. 169.254.169.254). A single request can return temporary IAM credentials, making this a high-value SSRF target for credential theft.

Content

Scanner excerpt · utils/url_validator.py (reported line 30)May include surrounding context.

python
# Blocked hostname patterns (homograph attacks, suspicious domains)
_BLOCKED_HOSTNAME_PATTERNS = [
    "localhost",
    "metadata.google.internal",  # GCP metadata
    "metadata.googleusercontent.com",  # GCP metadata
    "169.254.169.254",  # AWS/GCP/Azure metadata endpoints
]

Cloud Metadata Access

High
Category
Server-Side Request Forgery
Confidence
90% confidence
Finding

Code accesses a cloud instance metadata endpoint (e.g. 169.254.169.254). A single request can return temporary IAM credentials, making this a high-value SSRF target for credential theft.

Content

Scanner excerpt · utils/url_validator.py (reported line 32)May include surrounding context.

python
"localhost",
    "metadata.google.internal",  # GCP metadata
    "metadata.googleusercontent.com",  # GCP metadata
    "169.254.169.254",  # AWS/GCP/Azure metadata endpoints
]

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The README describes fetching via Jina, subtitle extraction via yt-dlp, and Whisper transcription via Groq, but does not clearly warn that user-supplied URLs and media may be sent to third-party services. In a skill context, this can lead to inadvertent disclosure of private URLs, content, or metadata to external providers during normal use.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The README states that after installing the Claude Code skill, users can 'just send a YouTube/Bilibili/podcast link' and the video skill auto-triggers. Broad auto-invocation on URL-shaped input can cause unintended processing of untrusted content, including automatic outbound requests and transcription of sensitive links the user did not explicitly intend to analyze.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The skill’s trigger scope is very broad: it activates on generic requests like analyzing a URL, article, or key takeaways, and even auto-triggers after transcription. This can cause unintended invocation on common user requests, leading to over-collection of content, accidental processing of sensitive material, or routing tasks to external fetch/transcription tools without clear user intent.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The CLI explicitly offers a browser-based login flow and the help text states that it saves a session for browser fallback, but the command path shown here does not provide any user-facing warning, consent step, or explanation of where/how session data will be stored. This can lead users to persist sensitive authentication material on disk without understanding the privacy and account-takeover implications if the local environment is shared or compromised.

Content

No source excerpt is available for this finding.

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 · fetchers/bilibili.py (reported line 10)May include surrounding context.

python
from typing import Dict, Any


API_URL = "https://api.bilibili.com/x/web-interface/view"
HEADERS = {
    "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36"
}

Unbounded Output

Medium
Category
Output Handling
Confidence
60% confidence
Finding

Output size or generation rate is not bounded. Unbounded output enables denial-of-service through resource exhaustion, log flooding, or context-window stuffing.

Content

Scanner excerpt · fetchers/twitter.py (reported line 40)May include surrounding context.

python
def _fetch_via_fxtwitter(url: str) -> Dict[str, Any]:
    """
    Fetch full tweet text via FxTwitter API.
    Free, no auth, returns complete text (no truncation).
    """
    match = re.search(r'x\.com/(\w+)/status/(\d+)', url)
    if not match:

Unbounded Output

Medium
Category
Output Handling
Confidence
60% confidence
Finding

Output size or generation rate is not bounded. Unbounded output enables denial-of-service through resource exhaustion, log flooding, or context-window stuffing.

Content

Scanner excerpt · fetchers/twitter.py (reported line 188)May include surrounding context.

python
def _fetch_via_fxtwitter(url: str) -> Dict[str, Any]:
    """
    Fetch full tweet text via FxTwitter API.
    Free, no auth, returns complete text (no truncation).
    """
    match = re.search(r'x\.com/(\w+)/status/(\d+)', url)
    if not match:

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The Playwright fallback automatically reuses a saved authenticated X session, which can cause requests to be made with the user's credentials and access level without an explicit runtime warning or consent gate. In an agent context, this increases the risk of unintentionally accessing private or login-gated content and leaking data that would not be available anonymously.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · fetchers/youtube.py (reported line 53)May include surrounding context.

python
]

        try:
            subprocess.run(cmd, capture_output=True, text=True, timeout=60)
        except FileNotFoundError:
            logger.warning("yt-dlp not found. Install with: brew install yt-dlp")
            return ""

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · fetchers/youtube.py (reported line 121)May include surrounding context.

python
]

        try:
            subprocess.run(cmd, capture_output=True, text=True, timeout=180)
        except FileNotFoundError:
            logger.warning("yt-dlp not found for audio download")
            return ""

External Transmission

Medium
Category
Data Exfiltration
Confidence
91% confidence
Finding

This code uploads downloaded audio derived from a user-requested YouTube URL to Groq's external transcription service. In a fetcher skill, this is contextually expected behavior, but it still creates a real data-egress/privacy boundary: content may be transmitted off-platform without explicit consent, policy enforcement, or content classification.

Content

Scanner excerpt · fetchers/youtube.py (reported line 150)May include surrounding context.

python
import requests
        try:
            with open(audio_path, "rb") as f:
                response = requests.post(
                    "https://api.groq.com/openai/v1/audio/transcriptions",
                    headers={"Authorization": f"Bearer {api_key}"},
                    files={"file": (os.path.basename(audio_path), f, "audio/mp4")},

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 · fetchers/youtube.py (reported line 151)May include surrounding context.

python
try:
            with open(audio_path, "rb") as f:
                response = requests.post(
                    "https://api.groq.com/openai/v1/audio/transcriptions",
                    headers={"Authorization": f"Bearer {api_key}"},
                    files={"file": (os.path.basename(audio_path), f, "audio/mp4")},
                    data={"model": "whisper-large-v3", "response_format": "text"},

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
85% confidence
Finding

The read_url and read_batch tools fetch content from arbitrary URLs, which necessarily involves outbound network requests and may disclose user-provided URLs or related system metadata to third-party services. While the code has docstrings describing functionality, it does not visibly warn users about the privacy implications of contacting external sites.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The method is presented as a reader/fetcher that returns structured content, but it also performs persistence side effects by writing to an inbox and saving markdown output. This can violate caller expectations, causing untrusted remote content to be stored locally without explicit opt-in, which increases risk of data leakage, unwanted persistence, and downstream unsafe handling of fetched content.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

Fetched content from arbitrary URLs is written to markdown automatically, with no explicit confirmation or visible warning in this code path. Because the content is attacker-controlled, automatic persistence can store sensitive, deceptive, or dangerous payloads for later rendering or processing, increasing the chance of phishing, prompt-injection persistence, or unsafe downstream consumption.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The function claims restrictive path validation, but its allowlist includes the entire home directory and /tmp, which greatly broadens the permitted write surface. It also uses a naive startswith() prefix check, so paths that merely share a prefix with an allowed directory can bypass the intended restriction, enabling writes to unintended filesystem locations if filepath is attacker-influenced.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
81% confidence
Finding

The supported-platform list is presented as handling WeChat, Bilibili, and Xiaohongshu alongside global platforms, but there is no visible language/locale choice anywhere in the CLI text. For a universal reader, the absence of any user-selectable language behavior can indicate a locale assumption that should be made explicit or optional.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
78% confidence
Finding

This code file makes an outbound HTTP request to Bilibili's API using data derived from the user's input, but the only disclosure is an internal log line and docstring. Under the rule, network calls that transmit user or system data should have some visible disclosure unless clearly covered by the skill description or obviously expected from the skill's stated purpose; this file alone does not provide a user-facing warning.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.