Back to skill

Security audit

local-search-pro

Security checks for vulnerabilities and agentic risk

Overview

The skill is mostly a coherent local SearXNG search installer, but it needs review because its installer and configuration carry avoidable local security and persistence risks.

Review before installing. This skill starts a persistent Docker service, replaces any existing container named searxng-local, pulls an unpinned SearXNG image, and has installer shell-quoting risk if installed under an unusual path. Keep SEARXNG_BASE_URL unset unless you intentionally trust the target, and remove the service with docker rm -f searxng-local when no longer needed.

Vulnerability Patterns
  • System PersistenceInstalls backdoors, hooks, services, or scheduled tasks that survive the run
  • 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
Findings (4)

T06 · System Persistence

Warning
Location
scripts/install.py:63
Finding

Persistent Auto-Restarting Docker Service

Content
View full analysis

Vulnerability Details

File Location: scripts/install.py, lines 63-69
Vulnerability Type: Persistent background service
Risk Level: Medium

Vulnerable Code

python
run(
    "docker run -d --name searxng-local "
    "-p 127.0.0.1:8080:8080 "
    f"-v {SETTINGS_FILE}:/etc/searxng/settings.yml "
    "--restart unless-stopped "
    "searxng/searxng"
)

Technical Analysis

The installer starts the container in detached mode with -d and applies the Docker restart policy unless-stopped. Consequently, the container continues running after the invoking process or agent session ends and may restart automatically when the Docker daemon or host restarts.

This persistence is disclosed in SKILL.md, and the container only publishes its port on 127.0.0.1. Nevertheless, it creates a cross-session system service rather than a process limited to the current skill invocation.

Attack Path

  1. A user or agent executes scripts/install.py.
  2. The script creates the detached searxng-local container.
  3. Docker records the unless-stopped restart policy.
  4. The skill invocation ends, but the container remains active.
  5. After a host or Docker daemon restart, Docker starts the container again unless it was explicitly stopped.

Impact Assessment

The persistent container can consume processor, memory, network, and storage resources outside the initiating session. It continues executing the selected container image with access to the mounted settings file. The service is restricted to localhost at the host-port level, reducing remote exposure, but any local process can potentially interact with it. Creation of the service requires access to the local Docker daemon.

Remediation
View remediation

Remediation Suggestions

  • Remove --restart unless-stopped from the default installation path.
  • Run the container in the foreground or use --rm when the service only needs to exist for the current session.
  • If persistence is required, require an explicit option such as --persistent and obtain informed user consent before creating the service.
  • Display the exact container lifetime and removal command before installation.
  • Add a supported uninstall routine that stops and removes the container and verifies that no persistent resources remain.
  • Consider applying resource limits such as --memory, --cpus, and --pids-limit.

T09 · Insecure Skill Coding Practices

Error
Location
scripts/install.py:11
Finding

Shell Command Injection Through Unquoted Installation Path

Content
View full analysis

Vulnerability Details

File Location: scripts/install.py, lines 11-15 and 63-69
Vulnerability Type: Shell command injection
Risk Level: High

Vulnerable Code

python
def run(cmd):
    result = subprocess.run(cmd, shell=True)
    if result.returncode != 0:
        sys.exit(result.returncode)
python
run(
    "docker run -d --name searxng-local "
    "-p 127.0.0.1:8080:8080 "
    f"-v {SETTINGS_FILE}:/etc/searxng/settings.yml "
    "--restart unless-stopped "
    "searxng/searxng"
)

Technical Analysis

SETTINGS_FILE is derived from the filesystem location of the skill and is interpolated directly into a command string passed to subprocess.run(..., shell=True). The path is neither shell-quoted nor passed as an isolated argument.

If the project resides in a directory whose name contains shell metacharacters, such as a semicolon, command substitution, or another shell control operator, the shell can interpret part of the path as a separate command. Spaces in an ordinary directory name can also break the intended Docker argument even when they do not lead to command execution.

Attack Path

  1. An attacker supplies or relocates the skill under a directory name containing shell syntax.
  2. A user or automation process executes scripts/install.py from that installation.
  3. Path(__file__).resolve() incorporates the crafted directory name into SETTINGS_FILE.
  4. The path is inserted into the Docker command without quoting.
  5. subprocess.run invokes a shell, which parses the embedded metacharacters.
  6. The injected command executes with the operating-system privileges of the user running the installer.

Exploitation requires influence over the skill's installation path or an equivalent ability to place the project in a maliciously named directory.

Impact Assessment

Successful exploitation permits arbitrary command execution as the installer user. This ...[truncated 387 chars]

Remediation
View remediation

Remediation Suggestions

  • Eliminate shell=True and pass commands as argument arrays.
  • Pass the complete volume specification as one argument so paths containing spaces or metacharacters are not parsed by a shell.
  • Use check=True for explicit error propagation.
  • Apply the same approach to the Docker version check and container-removal command.
  • Avoid attempting to reproduce shell constructs such as || true; instead, inspect return codes in Python.

Example hardened pattern:

python
subprocess.run(
    [
        "docker", "run", "-d",
        "--name", "searxng-local",
        "-p", "127.0.0.1:8080:8080",
        "-v", f"{SETTINGS_FILE}:/etc/searxng/settings.yml",
        "--restart", "unless-stopped",
        "searxng/searxng@sha256:PINNED_DIGEST",
    ],
    check=True,
)

T08 · Insecure Dependencies

Warning
Location
scripts/install.py:63
Finding

Execution of Unpinned Third-Party Dependencies

Content
View full analysis

Vulnerability Details

File Location: scripts/install.py, lines 63-69; SKILL.md, lines 31-37
Vulnerability Type: Unpinned package and container dependencies
Risk Level: Medium

Vulnerable Code

python
run(
    "docker run -d --name searxng-local "
    "-p 127.0.0.1:8080:8080 "
    f"-v {SETTINGS_FILE}:/etc/searxng/settings.yml "
    "--restart unless-stopped "
    "searxng/searxng"
)

The documentation also instructs users to install an unconstrained Python package:

text
Install if missing:

pip install requests

text

Technical Analysis

The Docker image is referenced as searxng/searxng without a version tag or immutable digest. Docker therefore resolves a mutable image reference, normally using the latest tag when no explicit tag is supplied. The code that ultimately executes can change after this skill has been reviewed.

Likewise, pip install requests does not specify an audited version or verify an artifact hash. Although both dependency names appear legitimate and the reviewed project contains no evidence that they are currently malicious, mutable and unconstrained dependencies weaken reproducibility and supply-chain integrity.

Attack Path

  1. A mutable upstream image or package changes, is compromised, or publishes an incompatible release.
  2. A user follows the installation instruction or runs the Docker installer on a host where the image is not already available.
  3. The package manager or Docker retrieves the then-current upstream artifact.
  4. The retrieved code executes locally with the permissions available to Python or the Docker environment.
  5. A compromised artifact could perform actions beyond the reviewed skill's intended behavior.

Impact Assessment

A compromised Python dependency would execute in the context of the user running the Python scripts. A compromised container image would execute through the local Docker daemon and ...[truncated 357 chars]

Remediation
View remediation

Remediation Suggestions

  • Pin the SearXNG image to a reviewed immutable digest, for example searxng/searxng@sha256:....
  • Establish a documented process for reviewing and deliberately updating that digest.
  • Pin requests to a tested version in a requirements file.
  • Use hash verification, such as pip install --require-hashes -r requirements.txt.
  • Prefer an isolated virtual environment rather than installing into a global Python environment.
  • Generate and retain a dependency lock file and include automated vulnerability scanning for both Python packages and container images.

T09 · Insecure Skill Coding Practices

Note
Location
scripts/install.py:33
Finding

Predictable Hardcoded SearXNG Secret Key

Content
View full analysis

Vulnerability Details

File Location: scripts/install.py, lines 33-48; duplicated in searxng/settings.yml, line 7
Vulnerability Type: Hardcoded cryptographic secret
Risk Level: Low

Vulnerable Code

python
SETTINGS_FILE.write_text(f"""
use_default_settings: true

server:
  bind_address: 0.0.0.0
  port: 8080
  secret_key: local_search_pro_secure_2026

search:
  safe_search: {safe_search}
  formats:
    - html
    - json

limiter:
  enabled: {limiter}
""")

The committed configuration contains the same value:

yaml
server:
  bind_address: 0.0.0.0
  port: 8080
  secret_key: local_search_pro_secure_2026

Technical Analysis

Every installation uses the same publicly visible and predictable secret_key. A server secret must be unique and generated with a cryptographically secure random source. A value committed to source code provides no secrecy and cannot safely protect signed state, sessions, or other integrity mechanisms that depend on it.

The Docker host-port mapping restricts the default service to 127.0.0.1, which substantially reduces remote exploitability. However, SearXNG binds to all interfaces inside the container, and the predictable secret becomes more consequential if the service is later exposed through a proxy, a modified Docker configuration, or another container network.

Attack Path

  1. An attacker reads the public skill source and obtains the shared secret.
  2. The attacker identifies an installation that uses the unchanged configuration.
  3. The attacker gains network reachability to the service, such as through local access, a proxy, or a subsequently broadened port mapping.
  4. If a SearXNG security mechanism relies on this key, the attacker attempts to forge or manipulate secret-dependent data.

The reviewed files do not establish a specific secret-dependent forgery primitive, so the practical impact depends on how the installed ...[truncated 405 chars]

Remediation
View remediation

Remediation Suggestions

  • Generate a unique secret during installation using a cryptographically secure source, such as secrets.token_urlsafe(32).
  • Do not commit an operational secret value to the repository.
  • Write the generated configuration with permissions limited to its owner, such as mode 0600.
  • Preserve an existing secret during routine reinstallation unless deliberate rotation is requested.
  • Document a safe secret-rotation procedure.
  • Continue enforcing the localhost-only host-port binding and warn users against exposing the service without authentication and transport security.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
Findings (18)

Tainted flow: 'BASE_URL' from os.environ.get (line 7, credential/environment) → requests.get (network output)

Critical
Category
Data Flow
Confidence
92% confidence
Finding

The healthcheck performs an outbound HTTP request to a URL taken directly from the SEARXNG_BASE_URL environment variable, so an attacker or untrusted deployment context can redirect it away from localhost. In a skill advertised as localhost-only, this weakens the trust boundary and can enable unintended external connections or probing of internal network endpoints.

Content

Scanner excerpt · scripts/healthcheck.py (reported line 12)May include surrounding context.

python
def main():
    try:
        r = requests.get(BASE_URL, timeout=5)
        if r.status_code == 200:
            print(f"OK: SearXNG reachable at {BASE_URL}")
            sys.exit(0)

Tainted flow: 'HEALTH_ENDPOINT' from os.environ.get (line 9, credential/environment) → requests.get (network output)

Critical
Category
Data Flow
Confidence
93% confidence
Finding

The request target is derived from the SEARXNG_BASE_URL environment variable, so an attacker or untrusted wrapper can redirect health checks to an arbitrary host instead of localhost. In this skill, that undermines the stated localhost-only trust boundary and can enable SSRF-style outbound requests or exfiltration of user search activity to a remote service.

Content

Scanner excerpt · scripts/search.py (reported line 14)May include surrounding context.

python
def health_check():
    try:
        r = requests.get(HEALTH_ENDPOINT, timeout=5)
        return r.status_code == 200
    except Exception:
        return False

Tainted flow: 'SEARCH_ENDPOINT' from os.environ.get (line 8, credential/environment) → requests.get (network output)

Critical
Category
Data Flow
Confidence
97% confidence
Finding

The search request sends the user's query to a URL built from an environment-controlled base, allowing a non-local or attacker-controlled endpoint to receive all searches. Because this tool is presented as a secure localhost-only search skill, redirectability materially increases risk by enabling silent data disclosure and arbitrary outbound network access.

Content

Scanner excerpt · scripts/search.py (reported line 26)May include surrounding context.

python
"format": "json"
    }
    try:
        r = requests.get(SEARCH_ENDPOINT, params=params, timeout=10)
        r.raise_for_status()
        return r.json()
    except requests.exceptions.ConnectionError:

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

The skill description presents the deployment as a secure localhost-only Brave API alternative, but the analyzed behavior includes Docker container management, a hardcoded static secret, and service configuration that binds to 0.0.0.0 inside the container. This mismatch is dangerous because it can cause users to trust a narrower and safer behavior model than what is actually deployed, potentially exposing the service more broadly than expected and embedding weak authentication material that may be reusable across installations.

Content

No source excerpt is available for this finding.

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
90% confidence
Finding

Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Content

Scanner excerpt · scripts/install.py (reported line 12)May include surrounding context.

python
def run(cmd):
    result = subprocess.run(cmd, shell=True)
    if result.returncode != 0:
        sys.exit(result.returncode)

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
90% confidence
Finding

Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Content

Scanner excerpt · scripts/install.py (reported line 18)May include surrounding context.

python
def docker_exists():
    return subprocess.call("docker --version > /dev/null 2>&1", shell=True) == 0


def write_settings(dev_mode=False):

Description-Behavior Mismatch

High
Category
Not specified by scanner
Confidence
99% confidence
Finding

The skill metadata explicitly claims a localhost-only deployment, but the configuration binds the server to 0.0.0.0, which exposes it on all network interfaces. This creates a trust and exposure mismatch: users may deploy it assuming it is only reachable locally, while it is actually accessible from the LAN or wider network depending on host firewall and container settings.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding

The skill advertises installation and runtime behavior that rely on shell, network, environment access, and file writes, but it does not declare any corresponding tool scope or permissions boundary. This is dangerous because users and orchestration systems cannot accurately assess or constrain what the skill is allowed to do, increasing the risk of unintended command execution, downloads, filesystem modification, and persistent local changes during installation or use.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The code claims a localhost-only deployment model but accepts SEARXNG_BASE_URL from the environment without enforcing that it resolves to a loopback address. That makes the healthcheck behavior broader than the stated security model and can be abused to contact arbitrary local-network or external services.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

An environment-configurable outbound target is broader than necessary for a simple local healthcheck and expands the attack surface beyond what the skill description suggests. In the context of a search skill marketed as secure and localhost-only, this mismatch increases the chance of SSRF-like misuse, unexpected telemetry, or policy bypass in deployment environments.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The installer automatically executes shell commands and forcibly removes any existing searxng-local container without warning or confirmation. While this is not remote code execution, it can cause unintended destructive changes on the host and is risky behavior for an install script users may run with trust.

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 · scripts/install.py (reported line 12)May include surrounding context.

python
def run(cmd):
    result = subprocess.run(cmd, shell=True)
    if result.returncode != 0:
        sys.exit(result.returncode)

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 · scripts/install.py (reported line 18)May include surrounding context.

python
def docker_exists():
    return subprocess.call("docker --version > /dev/null 2>&1", shell=True) == 0


def write_settings(dev_mode=False):

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The skill advertises a secure localhost-only deployment, but the generated SearXNG config binds the service to 0.0.0.0 and the hidden dev mode disables the limiter and safe search. Although Docker publishes only to 127.0.0.1 in this script, the internal service is configured for broader exposure and the hidden weakening of safeguards contradicts the stated security posture.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The hidden --dev flag is framed as a local-development aid, but it actually disables limiter protections and safe search, making the deployment meaningfully less safe rather than merely easier to develop against. Hidden insecure switches are especially concerning in an agent skill because users may not realize protections are being removed.

Content

No source excerpt is available for this finding.

Rp1

Medium
Category
MCP Rug Pull
Confidence
97% confidence
Finding

The container is launched from searxng/searxng without a version tag or digest, so installs are non-reproducible and may silently pull a changed or compromised image in the future. In a security-sensitive installer, trusting a mutable latest image materially increases supply-chain risk.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

By default the skill uses plain HTTP to send search queries, which provides no transport security if traffic ever leaves the local host or if the localhost assumption is broken via configuration. In combination with the environment-variable redirect, queries may be exposed in cleartext to local network observers or intermediaries.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The metadata claims a secure localhost-only deployment, but the implementation accepts SEARXNG_BASE_URL from the environment and uses it directly. That mismatch can mislead users and downstream agents into treating the skill as local-only when it can actually transmit requests to arbitrary systems.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.