Back to skill

Security audit

v2ex-monitor

Security checks for vulnerabilities and agentic risk

Overview

This V2EX monitoring skill is mostly purpose-aligned, but it handles the user's API key unsafely enough that it should be reviewed before installation.

Install only if you are comfortable giving the skill a V2EX API token and storing that token locally. Prefer using a limited/revocable token, avoid running it on untrusted networks until TLS verification is fixed, and treat any token used with this version as potentially exposed if MCP outputs or logs are retained.

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
v2ex_monitor.py:78
Finding
Authenticated API Requests Disable TLS Certificate Verification<![CDATA[ ## Vulnerability Details **File Location**: `v2ex_monitor.py:78-80`, `v2ex_monitor.py:106-108`, `v2ex_monitor.py:137-139`, and `v2ex_mcp.py:33-35` **Vulnerability Type**: Improper certificate validation **Risk Level**: High ### Vulnerable Code `v2ex_monitor.py:78-81`: ```python ctx = ssl.create_default_context() ctx.check_hostname = False ctx.verify_mode = ssl.CERT_NONE with urllib3.PoolManager(ssl_context=ctx) as pool: ``` The same pattern is repeated for topic details at lines 106-108 and notifications at lines 137-139. `v2ex_mcp.py:33-39`: ```python self.ctx = ssl.create_default_context() self.ctx.check_hostname = False self.ctx.verify_mode = ssl.CERT_NONE self.headers = { "Authorization": f"Bearer {api_key}", "User-Agent": "V2EX-MCP/1.0" } ``` ### Technical Analysis The clients create a normal TLS context and then explicitly disable hostname checks and certificate-chain validation. These contexts are used for authenticated requests containing the V2EX bearer token. Encryption without certificate authentication does not establish that the remote endpoint is V2EX. Any attacker capable of intercepting or redirecting network traffic can present an arbitrary certificate, terminate the connection, and receive the authorization header. The verified `requests` fallback in `v2ex_monitor.py` does not mitigate this flaw because it is only attempted after the insecure primary request fails. Globally suppressing `urllib3` warnings in `v2ex_monitor.py` further reduces the likelihood that users will notice insecure TLS behavior. ### Attack Path 1. A user configures a valid V2EX API token and invokes the monitor or MCP server. 2. An attacker obtains a network interception position, controls a proxy, compromises local DNS, or redirects traffic through a hostile access point. 3. The attacker redirects the connection intended for `www.v2ex.com` to an attacker-controlled TLS endpoint. 4. The hostile endpoint presents an untrusted or hostname-mismat ...[truncated 778 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Retain the defaults from `ssl.create_default_context()` and remove both of the following assignments: ```python ctx.check_hostname = False ctx.verify_mode = ssl.CERT_NONE ``` - Use a verified `urllib3.PoolManager` directly: ```python http = urllib3.PoolManager( cert_reqs="CERT_REQUIRED", ca_certs=ssl.get_default_verify_paths().cafile, ) ``` - Apply the same correction to every request path in `v2ex_monitor.py` and to `V2EXClient` in `v2ex_mcp.py`. - Remove `urllib3.disable_warnings()`. - Fail closed when certificate validation fails instead of retrying through another unverified client. - If a private certificate authority must be supported, accept an explicit CA bundle path rather than disabling verification. - Revoke and replace any API token previously used over untrusted networks with the vulnerable versions. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
v2ex_mcp.py:307
Finding
MCP Configuration Response Discloses the Complete API Key<![CDATA[ ## Vulnerability Details **File Location**: `v2ex_mcp.py:307-318` **Vulnerability Type**: Sensitive credential disclosure **Risk Level**: High ### Vulnerable Code ```python elif name == "v2ex_config": config = load_config() if arguments.get("nodes"): config["nodes"] = [n.strip() for n in arguments["nodes"].split(",")] if arguments.get("apikey"): config["apikey"] = arguments["apikey"] CONFIG_FILE.parent.mkdir(parents=True, exist_ok=True) with open(CONFIG_FILE, "w", encoding="utf-8") as f: json.dump(config, f, ensure_ascii=False, indent=2) return [TextContent(type="text", text=json.dumps({"状态": "配置已保存", "配置": config}, ensure_ascii=False))] ``` ### Technical Analysis The `v2ex_config` MCP tool returns the entire `config` object after saving it. Because that object contains the `apikey` property, the complete bearer token is placed in MCP tool output. Tool output may be retained in Agent transcripts, debugging logs, telemetry, model context, or orchestration-system records. Returning the secret is unnecessary to confirm that configuration succeeded and expands credential exposure beyond the process that originally supplied it. The handler also calls `get_client()` before entering its configuration branch. Consequently, the configuration tool cannot establish the first API key unless a valid key already exists, but this functional defect does not prevent disclosure when updating an existing configuration. ### Attack Path 1. A V2EX API key already exists in the configuration, or an authorized caller invokes `v2ex_config` to update it. 2. The MCP handler saves the configuration. 3. The handler serializes and returns the complete configuration, including the API key. 4. The MCP host, Agent transcript, logs, telemetry pipeline, or another party with access to tool results records the response. 5. A party able to read that retained output recovers the token. 6. The party uses the token against V2EX within i ...[truncated 374 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Never include the API key or complete configuration object in MCP output. - Return only non-sensitive confirmation data: ```python return [ TextContent( type="text", text=json.dumps( { "status": "configuration saved", "nodes": config.get("nodes", []), "apikey_configured": bool(config.get("apikey")), } ), ) ] ``` - Redact credentials consistently in logs, exceptions, CLI output, traces, and telemetry. - Move `get_client()` into only those branches that perform API requests so that initial configuration does not require an existing key. - Restrict access to the configuration tool to trusted callers where the MCP host supports tool-level authorization. - Rotate any token that may already have appeared in stored MCP transcripts or logs. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
v2ex_monitor.py:172
Finding
API Key Is Persisted in a Plaintext Configuration File Without Explicit Access Restrictions<![CDATA[ ## Vulnerability Details **File Location**: `v2ex_monitor.py:172-175` **Vulnerability Type**: Insecure storage of sensitive information **Risk Level**: Medium ### Vulnerable Code ```python def save_config(config: dict): """保存配置""" CONFIG_FILE.parent.mkdir(parents=True, exist_ok=True) with open(CONFIG_FILE, "w", encoding="utf-8") as f: json.dump(config, f, ensure_ascii=False, indent=2) ``` The saved object includes the API key assigned by the configuration command: ```python if args.apikey: config["apikey"] = args.apikey save_config(config) ``` The MCP configuration path in `v2ex_mcp.py:314-316` independently performs the same unrestricted plaintext write: ```python CONFIG_FILE.parent.mkdir(parents=True, exist_ok=True) with open(CONFIG_FILE, "w", encoding="utf-8") as f: json.dump(config, f, ensure_ascii=False, indent=2) ``` ### Technical Analysis The bearer token is stored directly in `v2ex_monitor_config.json`. The code does not explicitly create the file with owner-only permissions or correct permissions on an existing file. Effective access therefore depends on the operating system, current umask, parent directory permissions, backup configuration, and packaging practices. Because the file is located in the project directory, it may also be accidentally committed, archived, copied with the Skill, or exposed to other processes and users that can read the project tree. ### Attack Path 1. A user invokes the CLI or MCP configuration command with a V2EX API key. 2. The Skill writes the key in plaintext to `v2ex_monitor_config.json`. 3. The file inherits permissions determined by the environment rather than an explicit owner-only policy. 4. Another local user, process, backup reader, archive recipient, or source-control user obtains the project directory or configuration file. 5. The party reads the API key and uses it against V2EX within the token's permission scope. ### Impact Assessment This issue does not indep ...[truncated 315 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Prefer reading the token from an environment variable or operating-system secret store rather than saving it in the project directory. - If file storage is required on POSIX systems, create the file atomically with mode `0600` and verify or correct existing permissions: ```python import os fd = os.open(CONFIG_FILE, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600) with os.fdopen(fd, "w", encoding="utf-8") as f: json.dump(config, f, ensure_ascii=False, indent=2) os.chmod(CONFIG_FILE, 0o600) ``` - On Windows, apply an ACL granting access only to the intended account. - Add `v2ex_monitor_config.json` to `.gitignore` and packaging exclusions. - Keep a non-secret example file containing an empty placeholder rather than a value that could be mistaken for a real credential. - Document token rotation and revocation procedures. - Apply the same secure storage implementation to both CLI and MCP configuration paths. ]]>

T08 · Insecure Dependencies

Warning
Location
requirements.txt:1
Finding
Third-Party Dependencies Are Installed Without Version or Integrity Pinning<![CDATA[ ## Vulnerability Details **File Location**: `requirements.txt:1-4` **Vulnerability Type**: Uncontrolled dependency resolution **Risk Level**: Medium ### Vulnerable Code ```text urllib3 mcp pydantic requests ``` ### Technical Analysis All dependencies are specified only by package name. Installation therefore resolves whatever versions are available from the configured package index at installation time. Builds are not reproducible, and the project cannot demonstrate that an installed dependency matches a reviewed release. The listed names are standard packages, and the audit found no direct evidence of typosquatting or an intentionally malicious dependency. The risk arises from unrestricted future resolution, compromised package releases or indexes, and unexpected security or compatibility regressions. ### Attack Path 1. A user follows the documented installation command and runs `pip install -r requirements.txt`. 2. `pip` queries the configured package index and selects the latest versions satisfying the unconstrained entries. 3. A compromised account, package-index incident, hostile mirror, or future unsafe release supplies dependency code that was not part of this audit. 4. The dependency is installed into the Skill environment. 5. Its code executes when imported or invoked by the Skill, with the privileges of the user running the Skill. ### Impact Assessment A compromised dependency can execute arbitrary Python code under the Skill process's operating-system account. This could expose the V2EX API key, configuration, reports, accessible local files, and network resources. Actual impact depends on the process account's permissions; the Skill itself does not request administrative privileges. ]]>
Remediation
<![CDATA[ ## Remediation Suggestions - Pin each dependency to a reviewed version or tightly controlled compatible range. - Generate and commit a lock file with cryptographic hashes, for example through `pip-tools`: ```bash pip-compile --generate-hashes requirements.in pip install --require-hashes -r requirements.txt ``` - Install only from trusted package indexes over verified TLS. - Use dependency scanning and review security advisories before updating pins. - Test updates in an isolated environment before deployment. - Remove optional dependencies that are not required for the selected execution mode to reduce supply-chain exposure. ]]>
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 (19)

Intent-Code Divergence

High
Confidence
99% confidence
Finding
This code disables both certificate verification and hostname checking for HTTPS requests made via urllib3 by setting check_hostname to False and verify_mode to ssl.CERT_NONE. That allows a man-in-the-middle attacker to intercept or modify API responses and capture the Bearer API key, which is especially risky because the tool authenticates to a remote service and trusts returned JSON.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The documentation instructs users to supply a V2EX API key and use features that retrieve notifications and account-linked information, but it does not clearly warn that the key will be transmitted to V2EX and that account-specific data will be fetched and stored locally. This can lead users to expose sensitive credentials or personal account data without informed consent, especially when the skill is integrated into higher-automation agent environments.

Natural-Language Policy Violations

Medium
Confidence
96% confidence
Finding
The module docstring and all user-facing CLI descriptions are written only in Chinese, which imposes a specific language on users without any opt-in or alternative locale support. Under the policy, forcing a specific language without user choice is a natural-language policy violation.

Natural-Language Policy Violations

Medium
Confidence
84% confidence
Finding
The natural-language descriptions and user-facing strings in this file are entirely in Chinese, with no indication that language is configurable or intentionally restricted to a Chinese-only audience. Under the language/locale policy, forcing a specific language without opt-in can be a policy violation.

Intent-Code Divergence

Medium
Confidence
97% confidence
Finding
The tool dispatcher calls get_client() before checking which tool was requested, so even the v2ex_config tool cannot run unless an API key is already present. This creates a denial-of-service/bootstrap failure for the skill's own configuration path and can prevent recovery or first-time setup.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The v2ex_config tool persists the API key in a JSON file on disk in plaintext, with no warning, no file permission hardening, and no safer secret-storage option. Any local user, backup system, log collector, or later-compromised process with access to that directory could recover the token and use it to access the user's V2EX account data.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The API key is stored in plaintext in a local JSON config file without warnings, permission hardening, or use of a secure secret store. On multi-user systems, shared workstations, backups, or compromised local accounts, this can expose the credential and allow unauthorized access to the V2EX account or API usage.

Natural-Language Policy Violations

Low
Confidence
95% confidence
Finding
The natural-language instructions, headings, usage guidance, and warnings are all presented in Chinese, which effectively forces a specific language for users of the skill. There is no opt-in, alternate language option, or explanation that the skill is intended only for a Chinese-language audience or region-specific use case.

Unpinned Dependencies

Low
Category
Supply Chain
Content
urllib3
mcp
pydantic
requests
Confidence
97% confidence
Finding
The dependency 'urllib3' is unpinned, so builds may resolve to different versions over time, including versions with known security defects or breaking behavior. In a security-sensitive skill, this weakens supply-chain integrity and makes it impossible to verify whether a safe version is consistently installed.

Unverifiable Dependency: urllib3 has 16 known advisory(ies) (CVE-2025-66471 (urllib3 streaming API improperly handles highly compressed data); CVE-2024-37891 (urllib3's Proxy-Authorization request header isn't stripped during cross-origin ); CVE-2026-21441 (Decompression-bomb safeguards bypassed when following HTTP redirects (streaming ) +13 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
95% confidence
Finding
The manifest does not pin a version for 'urllib3', and the package has multiple known advisories, so the actual installed version may be vulnerable without any visibility from this file alone. Because urllib3 is a core HTTP client component, affected versions could expose the skill to request handling flaws, credential leakage, or denial-of-service conditions depending on runtime usage.

Unpinned Dependencies

Low
Category
Supply Chain
Content
urllib3
mcp
pydantic
requests
Confidence
97% confidence
Finding
The dependency 'mcp' is unpinned, which allows environment-dependent resolution to potentially vulnerable or incompatible releases. Because MCP-related packages may expose protocol/server behavior, lack of version control increases uncertainty and supply-chain risk.

Unverifiable Dependency: mcp has 12 known advisory(ies) (CVE-2025-53366 (MCP Python SDK vulnerability in the FastMCP Server causes validation error, lead); CVE-2025-66416 (Model Context Protocol (MCP) Python SDK does not enable DNS rebinding protection); CVE-2026-52870 (MCP Python SDK: Experimental task handlers allow any client to access and cancel) +9 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
96% confidence
Finding
The 'mcp' package is unpinned despite multiple known advisories, and MCP libraries can directly influence server/client protocol security behavior. If an affected version is installed, the skill could inherit vulnerabilities such as access control weaknesses, DNS rebinding exposure, or validation flaws that materially increase attack surface.

Unpinned Dependencies

Low
Category
Supply Chain
Content
urllib3
mcp
pydantic
requests
Confidence
96% confidence
Finding
The dependency 'pydantic' is unpinned, so installations are not reproducible and may pull in vulnerable or behavior-changing releases. This is a common dependency hygiene issue that can enable avoidable exposure when upstream security issues are disclosed.

Unverifiable Dependency: pydantic has 4 known advisory(ies) (CVE-2021-29510 (Use of "infinity" as an input to datetime and date fields causes infinite loop i); CVE-2024-3772 (Pydantic regular expression denial of service); CVE-2021-29510 (Pydantic is a data validation and settings management using Python type hinting.) +1 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
93% confidence
Finding
The manifest leaves 'pydantic' unpinned even though there are known advisories affecting some versions, making the deployed security posture unverifiable. If an affected release is installed, the skill may be exposed to denial-of-service or parsing/validation-related bugs depending on how user input is processed.

Unpinned Dependencies

Low
Category
Supply Chain
Content
urllib3
mcp
pydantic
requests
Confidence
97% confidence
Finding
The dependency 'requests' is unpinned, allowing future installs to resolve unpredictably and potentially include versions affected by known vulnerabilities. For networking libraries, this creates unnecessary risk because security posture can change without code changes.

Unverifiable Dependency: requests has 16 known advisory(ies) (CVE-2014-1830 (Exposure of Sensitive Information to an Unauthorized Actor in Requests); CVE-2024-47081 (Requests vulnerable to .netrc credentials leak via malicious URLs); CVE-2024-35195 (Requests `Session` object does not verify requests after making first request wi) +13 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
95% confidence
Finding
The 'requests' dependency is unpinned while multiple known advisories exist, so this requirements file cannot demonstrate that a non-vulnerable version will be installed. Since requests often handles outbound HTTP, affected versions may enable credential leakage, TLS-related issues, or unsafe URL handling in downstream code.

Missing User Warnings

Low
Confidence
84% confidence
Finding
This markdown file states that data will be stored in a local `v2ex_monitor_data/` directory, which affects user data on disk. The description does not include any warning or disclosure about what is retained, how long it persists, or that running the skill will create local files in the working directory.

Missing User Warnings

Low
Confidence
78% confidence
Finding
The tool descriptions for v2ex_get_notifications and v2ex_get_my_info indicate retrieval of current-user notifications and personal information, which are privacy-relevant remote API operations. The file contains no user-facing warning or disclosure about accessing account-specific data beyond the functional description.

Natural-Language Policy Violations

Low
Confidence
94% confidence
Finding
The file's natural-language interface and generated output are entirely in Chinese, including descriptions, status messages, and report content. Because the skill does not offer a language selection or document a justified locale restriction, it violates the language/locale policy described for this audit.

Static analysis

No suspicious patterns detected.