Back to skill

Security audit

web-searxng

Security checks for vulnerabilities and agentic risk

Overview

This SearXNG search skill is not malicious, but it needs review because private search terms can be sent to unverified services and cached locally.

Install only if you trust the configured SearXNG endpoint and understand the Chinese documentation. Set SEARXNG_URL explicitly to a trusted instance, avoid sensitive queries unless caching and endpoint routing are acceptable, clear scripts/.cache when needed, and treat stock output as generic generated analysis rather than financial advice.

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

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/searxng.py:72
Finding
Unvalidated SearXNG Endpoint Can Disclose Search Queries to an Unintended Service## Vulnerability Details **File Location**: `scripts/searxng.py`, lines 72-74, 101-117, and 168-171 **Vulnerability Type**: Unvalidated service endpoint and unsafe service discovery **Risk Level**: Medium ### Vulnerable Code ```python env_url = os.getenv("SEARXNG_URL") if env_url: return env_url.rstrip('/') ``` ```python common_ports = [8080, 8888, 9000, 8000] for port in common_ports: try: import socket sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(1) result = sock.connect_ex(("localhost", port)) sock.close() if result == 0: url = f"http://localhost:{port}" print(f"Found service at port {port}, assuming SearXNG: {url}", file=sys.stderr) return url except Exception: pass ``` ```python params = {'q': query, 'format': 'json'} url = f"{self.base_url}/search" try: async with session.get(url, params=params) as response: ``` ### Technical Analysis The `SEARXNG_URL` environment variable is accepted without validating its scheme, host, port, credentials, or path. Automatic discovery also assumes that any process listening on one of several common localhost ports is a SearXNG instance. No application-level request is performed to verify the identity of the selected service before user queries are transmitted. The selected endpoint receives the query through an HTTP GET request. Plain HTTP is permitted, and `aiohttp` follows redirects by default. Consequently, a malicious or incorrectly identified service can receive potentially sensitive search terms. If an attacker can influence the environment variable or operate a process on a probed port, the attacker can control where requests are sent. ### Attack Path 1. An attacker influences `SEARXNG_URL`, controls a service on one of the automatically probed localhost ports, or controls a selected endpoint that retu ...[truncated 839 chars]
Remediation
## Remediation Suggestions - Parse endpoints with `urllib.parse.urlsplit` and allow only explicitly supported `http` and `https` schemes. - Reject embedded credentials, malformed hosts, unexpected paths, and unsupported URL schemes. - Require HTTPS for non-loopback endpoints. If local plain HTTP is necessary, restrict it to validated loopback addresses. - Apply an explicit allowlist of approved hosts or network ranges where deployment requirements permit it. - Verify discovered services through a SearXNG-specific API request and expected response structure rather than relying only on an open TCP port. - Disable redirects with `allow_redirects=False`, or validate every redirect target before following it. - Avoid silently selecting an arbitrary service on a common port. Require explicit confirmation or fail closed when service identity cannot be established. - Document the trust requirements for `SEARXNG_URL` and ensure untrusted callers cannot modify the process environment.

T09 · Insecure Skill Coding Practices

Note
Location
scripts/searxng.py:129
Finding
Search Results Are Stored in a Plaintext Cache with Unrestricted Default Permissions## Vulnerability Details **File Location**: `scripts/searxng.py`, lines 129-134 and 151-160 **Vulnerability Type**: Plaintext storage of potentially sensitive search data **Risk Level**: Low ### Vulnerable Code ```python CACHE_DIR = os.path.join(os.path.dirname(__file__), ".cache") CACHE_TTL = 3600 # 1 hour in seconds # Ensure cache directory exists os.makedirs(CACHE_DIR, exist_ok=True) ``` ```python async def _set_cache(self, query_hash, results): cache_path = await self._get_cache_path(query_hash) cache_data = { 'timestamp': time.time(), 'results': results } async with aiofiles.open(cache_path, mode='w') as f: await f.write(json.dumps(cache_data)) ``` The cache identifier is generated as follows: ```python query_hash = hashlib.md5(query.encode()).hexdigest() ``` ### Technical Analysis Search-result titles, URLs, snippets, and timestamps are written in plaintext beneath `scripts/.cache`. The cache directory and files rely on process umask and default filesystem permissions; the code does not explicitly restrict access to the current user. Although the raw query is not stored directly, the filename is an unsalted MD5 digest of the query. Search queries often come from a predictable vocabulary. A local actor can calculate MD5 hashes for candidate queries and compare them with cache filenames, allowing dictionary-based recovery or confirmation of sensitive searches. The cached result content may independently reveal the subject of the query. The one-hour logical cache TTL only controls whether the application reuses the data. Expired files are not deleted, so sensitive results can remain on disk indefinitely. ### Attack Path 1. A user performs a sensitive search through the skill. 2. The script calculates an unsalted MD5 digest of the query. 3. Search results are written as plaintext to `scripts/.cache/<digest>.json`. 4. A local user, proces ...[truncated 724 chars]
Remediation
## Remediation Suggestions - Store cache files in an operating-system-provided, per-user cache directory rather than inside the skill package. - Create the cache directory with mode `0700` and cache files with mode `0600` on platforms that support POSIX permissions. - Use secure file creation that prevents symlink following and unintended replacement. - Provide a configuration option to disable caching for sensitive searches. - Delete expired cache entries rather than merely declining to reuse them. - If query-derived filenames are required, use an HMAC with a randomly generated private key instead of an unsalted MD5 digest. - Consider encrypting cached data where the local threat model includes other users, shared backups, or artifact collection. - Avoid caching fields that are not necessary for the skill's functionality.

other

Warning
Location
scripts/searxng.py:452
Finding
Untrusted Search Results Are Emitted as Unsanitized Markdown## Vulnerability Details **File Location**: `scripts/searxng.py`, lines 452-461 **Vulnerability Type**: Indirect prompt injection and unsafe Markdown rendering **Risk Level**: Medium ### Vulnerable Code ```python md_output.append("## 📋 搜索结果列表") for i, item in enumerate(processed_data[:15], 1): md_output.append(f"### {i}. {item['title']}") md_output.append(f"**来源**: {item['url']}") md_output.append(f"**摘要**: {item['snippet'][:150]}...") md_output.append("") if len(processed_data) > 15: md_output.append("---") md_output.append(f"### 其他结果 ({len(processed_data) - 15} 条)") for i, item in enumerate(processed_data[15:], 16): md_output.append(f"{i}. [{item['title']}]({item['url']})") ``` ### Technical Analysis Search-result titles, snippets, and URLs originate from external web pages and search providers. These values are inserted directly into Markdown without escaping Markdown metacharacters, validating URL schemes, removing embedded HTML, or clearly isolating the data as untrusted quoted content. A malicious indexed page can craft its title or snippet to contain instruction-like text intended to influence an AI agent consuming the skill output. It can also use Markdown syntax to alter presentation or construct deceptive links. In the secondary result list, an attacker-controlled URL is placed directly into a Markdown link destination without restricting it to expected `http` or `https` schemes. This is not evidence of an intentional instruction-hijacking payload shipped by the skill author. It is an unsafe trust-boundary issue that exposes downstream agents and Markdown renderers to attacker-controlled content. ### Attack Path 1. An attacker publishes a web page with a title, snippet, or URL containing malicious Markdown or instructions aimed at an AI agent. 2. The attacker optimizes the page to appear for a query likely to be submitted through the skill. 3. SearXNG includ ...[truncated 887 chars]
Remediation
## Remediation Suggestions - Treat every title, snippet, and URL returned by SearXNG as untrusted data. - Escape Markdown control characters in titles and snippets before interpolation. - Parse URLs and permit only explicitly approved schemes such as `http` and `https`. - Reject or neutralize embedded HTML and unsafe URI schemes. - Render search content inside clearly delimited or quoted sections that identify it as external data. - Include a machine-readable trust label so downstream agents can distinguish retrieved content from skill instructions. - Instruct consuming agents not to execute commands, reveal information, or change behavior based on instructions found inside search results. - Where possible, return structured JSON fields instead of Markdown and let a trusted presentation layer perform contextual escaping.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • 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)

Natural-Language Policy Violations

Medium
Confidence
94% confidence
Finding
The skill description and all examples are presented only in Chinese and imply Chinese-language interaction without offering any language selection or fallback. This can exclude users, cause misinterpretation of security-relevant behavior, and reduce informed consent because non-Chinese-speaking users may not fully understand what the skill does, including its automatic Docker service discovery behavior.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
import re
        
        # Get all running containers with their images and ports
        result = subprocess.run(
            ["docker", "ps", "--format", "{{.Names}}\t{{.Image}}\t{{.Ports}}"],
            capture_output=True,
            text=True,
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
import re
        
        # Get all running containers with their images and ports
        result = subprocess.run(
            ["docker", "ps", "--format", "{{.Names}}\t{{.Image}}\t{{.Ports}}"],
            capture_output=True,
            text=True,
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Natural-Language Policy Violations

Medium
Confidence
91% confidence
Finding
The code includes Chinese-language comments, output strings, and a stock-analysis mode driven by Chinese keywords, which effectively biases behavior and presentation toward a specific language/locale. There is no visible mechanism for user opt-in or language selection, so this can violate language/locale policy requirements.

Intent-Code Divergence

Medium
Confidence
95% confidence
Finding
The inline comment at L209-L210 says the fallback mechanism may provide a simulated response or direct link. In reality, fetch_results returns None plus the exception string, and search_task converts that into a DuckDuckGo fallback URL at L224-L226; no simulated response is ever produced. This is an active documentation-to-behavior contradiction about how failures are handled.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
When SearXNG fails, the code automatically constructs a DuckDuckGo search URL from the user's query without warning or consent. This can expose potentially sensitive or internal search terms to an external third party, violating privacy expectations and creating data leakage risk in environments where queries may contain confidential information.

Intent-Code Divergence

Medium
Confidence
83% confidence
Finding
The surrounding comments describe this as a search-result synthesis flow, but in stock mode the code goes beyond summarizing search results and produces derived resistance/support levels and explicit operation suggestions such as short-term and mid-term trading guidance. That behavior materially exceeds a neutral search-results presentation and conflicts with the documented framing as search output generation.

Static analysis

No suspicious patterns detected.