Back to skill

Security audit

free-local-web-search

Security checks for vulnerabilities and agentic risk

Overview

This skill mostly does what it advertises, but its local-only safety claim is weakened by broad URL overrides and a risky persistent Docker installer.

Review before installing. Use only if you are comfortable with a persistent local Docker service, and avoid running it from paths with spaces or shell metacharacters. Prefer a pinned SearXNG image, validate or remove SEARXNG_BASE_URL overrides, and check whether an existing searxng-local container belongs to you before running the installer.

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

T06 · System Persistence

Warning
Location
scripts/install.py:56
Finding

Persistent Docker Service Enabled by Default

Content
View full analysis

Vulnerability Details

File Location: scripts/install.py:56-63
Vulnerability Type: Persistent container service with an automatic restart policy
Risk Level: Medium

Vulnerable Code

python
run("docker rm -f searxng-local > /dev/null 2>&1 || true")

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 launches a detached Docker container with the --restart unless-stopped policy. Docker records this policy and automatically relaunches the container when the Docker daemon or host restarts unless an operator explicitly stops or removes it.

The persistent behavior is disclosed in SKILL.md, and removal instructions are provided. Nevertheless, executing the installer modifies long-lived system state beyond the immediate Skill invocation. The container continues running after the script exits and may return after a reboot.

The service port is mapped only to 127.0.0.1, which limits direct remote exposure. However, the persistent container still consumes system resources, retains access to the mounted configuration, and continues executing the selected image until removed.

Attack Path

  1. A user or agent executes python scripts/install.py.
  2. The script removes any existing container named searxng-local.
  3. Docker launches the new container in detached mode.
  4. Docker stores the unless-stopped restart policy.
  5. The container continues running after the installer terminates.
  6. Following a Docker daemon or host restart, Docker automatically launches the container again.
  7. The service remains persistent until an operator stops or removes it.

Impact Assessment

This behavior obtains no additional operating-system privileges by itself; the container runs with the privileges granted by the invokin ...[truncated 419 chars]

Remediation
View remediation

Remediation Suggestions

  • Do not enable an automatic restart policy by default. Use --restart no or omit the option.
  • Require explicit, informed user consent before enabling persistence, such as a clearly documented --persistent option.
  • Display the exact persistent system changes before applying them.
  • Provide commands to stop the service, remove the container, and reset its restart policy:
    bash
    docker update --restart=no searxng-local
    docker stop searxng-local
    docker rm searxng-local
    
  • Pin the container image to a reviewed immutable digest so each automatic restart executes the same audited artifact.
  • Consider foreground or ephemeral operation with --rm when persistence is not required.

T09 · Insecure Skill Coding Practices

Error
Location
scripts/install.py:9
Finding

Shell Command Injection Through an Unquoted Installation Path

Content
View full analysis

Vulnerability Details

File Location: scripts/install.py:9-12, 58-63
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"
)

SETTINGS_FILE is derived from the script's installation path:

python
BASE_DIR = Path(__file__).resolve().parent.parent
CONFIG_DIR = BASE_DIR / "searxng"
SETTINGS_FILE = CONFIG_DIR / "settings.yml"

Technical Analysis

The run helper passes a string to subprocess.run with shell=True. The settings path is interpolated directly into that command without shell quoting or argument separation.

Although the path is not supplied through a normal command-line parameter, it is derived from the directory in which the Skill is installed. If an attacker can cause the project to be extracted, copied, or invoked from a directory whose name contains shell metacharacters, those characters are interpreted by the shell. Ordinary spaces in the path can also split the Docker volume argument and cause incorrect behavior.

Examples of relevant metacharacters include semicolons, command substitutions, redirection operators, and shell control operators. Exploitation depends on control over the installation path and on convincing a user or agent with Docker access to run the installer.

Attack Path

  1. An attacker prepares or selects a parent directory name containing shell syntax.
  2. The project is extracted or copied beneath that directory.
  3. A user or agent executes scripts/install.py.
  4. Path(__file__).resolve() incorporates the attacker-controlled directory text into SETTINGS_FILE.

...[truncated 994 chars]

Remediation
View remediation

Remediation Suggestions

Eliminate shell interpretation and pass every command argument as a separate list element:

python
def run(cmd):
    subprocess.run(cmd, check=True)

subprocess.run(
    ["docker", "rm", "-f", "searxng-local"],
    stdout=subprocess.DEVNULL,
    stderr=subprocess.DEVNULL,
    check=False,
)

run([
    "docker",
    "run",
    "-d",
    "--name",
    "searxng-local",
    "-p",
    "127.0.0.1:8080:8080",
    "-v",
    f"{SETTINGS_FILE}:/etc/searxng/settings.yml:ro",
    "--restart",
    "no",
    "searxng/searxng@sha256:<verified-digest>",
])

Additional hardening should include:

  • Avoid shell=True for all Docker checks and lifecycle operations.
  • Replace shell redirection with stdout=subprocess.DEVNULL and stderr=subprocess.DEVNULL.
  • Replace || true with explicit return-code handling.
  • Mount configuration read-only if SearXNG does not need to modify it.
  • Add tests using installation paths containing spaces and shell metacharacters.
  • Validate and log the resolved configuration path before invoking Docker.

T08 · Insecure Dependencies

Warning
Location
scripts/install.py:58
Finding

Unpinned Python Package and Docker Image Dependencies

Content
View full analysis

Vulnerability Details

File Location: SKILL.md:23-27; scripts/install.py:58-63
Vulnerability Type: Unpinned third-party dependencies and non-reproducible installation
Risk Level: Medium

Vulnerable Code

The documentation instructs users to install the latest available requests package without a version or integrity constraint:

text
The runtime scripts require the Python package `requests`.

Install if missing:

pip install requests

text

The installer also runs an image by a mutable repository reference without a version tag or immutable digest:

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 Python installation instruction does not pin requests to a reviewed version and does not verify package hashes. The Docker image reference searxng/searxng has no explicit tag, so Docker resolves it to the mutable default tag, normally latest.

Consequently, installations are not reproducible. The effective third-party code may change after this Skill has been audited even when the Skill's own files remain unchanged. If an upstream distribution channel, maintainer account, package release, image tag, or registry is compromised, users could retrieve and execute an altered artifact.

The audit found no evidence that either named dependency is currently malicious. The issue is the absence of version and integrity controls, not a confirmed compromise of those upstream projects.

Attack Path

  1. An upstream package release, mutable Docker tag, maintainer account, or distribution channel is compromised or unexpectedly changed.
  2. A user follows SKILL.md and runs pip install requests, or executes scripts/install.py.
  3. The package manager or Docker resolves the unconstrained dependency to the ...[truncated 1041 chars]
Remediation
View remediation

Remediation Suggestions

  • Declare Python dependencies in a requirements or lock file using reviewed exact versions.
  • Verify Python package artifacts with hashes, for example:
    text
    requests==<reviewed-version> --hash=sha256:<verified-hash>
    
  • Pin the SearXNG image to an immutable reviewed digest:
    text
    searxng/searxng@sha256:<verified-digest>
    
  • Record the source, review date, version, and digest of every dependency.
  • Use automated vulnerability scanning for both Python packages and container images.
  • Establish a controlled update process that reviews release notes, regenerates hashes, rescans artifacts, and tests functionality before changing pinned versions.
  • Avoid automatically replacing an existing container until the newly selected image has passed integrity and security checks.
Vulnerability Patterns
  • 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
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (17)

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

Critical
Category
Data Flow
Confidence
97% confidence
Finding

The healthcheck sends an HTTP request to a URL fully controlled by the SEARXNG_BASE_URL environment variable. In a skill described as localhost-only, this allows the process to be coerced into probing arbitrary internal or external hosts, creating an SSRF-like network primitive and violating the stated trust boundary.

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
98% confidence
Finding

The code builds HEALTH_ENDPOINT from the SEARXNG_BASE_URL environment variable and sends a request to it without validating that it remains localhost-only. That permits server-side request forgery style behavior or arbitrary outbound connections if an attacker can influence the environment, which directly contradicts the skill’s claimed localhost-only scope and can be abused to probe internal services or exfiltrate request metadata.

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
99% confidence
Finding

SEARCH_ENDPOINT is derived from an environment-controlled base URL and then queried with user-supplied search text, enabling arbitrary outbound HTTP requests to non-local destinations. In practice this expands a local search helper into a general-purpose network client, which can be used to reach attacker-controlled hosts, internal network targets, or services not intended by the manifest.

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
96% confidence
Finding

The skill claims to be 'localhost-only' and 'secure', but the finding indicates behavior can be redirected to arbitrary remote endpoints via SEARXNG_BASE_URL, undermining the core safety claim. That mismatch is dangerous because users and agents may trust the skill with network access under false assumptions, enabling data exfiltration, SSRF-like outbound access, or silent use of untrusted search infrastructure.

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 advertises a localhost-only deployment, but the generated SearXNG configuration binds the application to 0.0.0.0 inside the container. Although Docker publishes it to 127.0.0.1 on the host in this script, the mismatch undermines the stated security model and increases exposure if the container networking or port publishing is later changed, reused, or copied into another environment.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding

The skill advertises installation and runtime behavior that requires shell, network, file write, and environment access, but it declares no explicit tool scope or permissions boundaries. This creates a confused-deputy risk where the agent may grant broader capabilities than users expect, especially because the skill installs packages and deploys a persistent Docker container.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

Although the skill claims a localhost-only deployment, the code permits any URL via SEARXNG_BASE_URL and then fetches it during the healthcheck. This mismatch makes the feature more dangerous in context because operators may assume the script cannot reach non-local targets when it actually can.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

For a simple local service healthcheck, letting an environment variable control an outbound HTTP destination is unnecessary and expands the attack surface. If an attacker can influence the environment, they can redirect the check to arbitrary services for reachability testing, metadata probing, or policy bypass.

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):

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The hidden --dev flag disables safe search and turns off the limiter despite the skill being described as secure and localhost-only. Hidden insecure modes are dangerous because users may not realize they are weakening protections, and the mismatch between marketing and behavior is itself a red flag under this skill's context.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The script force-removes an existing container named searxng-local with no confirmation or backup behavior. This can destroy an existing deployment, interrupt service, and discard state unexpectedly, which is especially risky in an installer users may trust to be non-destructive.

Content

No source excerpt is available for this finding.

Rp1

Medium
Category
MCP Rug Pull
Confidence
97% confidence
Finding

The script pulls and runs the searxng/searxng image without a pinned tag or digest, making the deployed code mutable over time. This creates a supply-chain risk: a compromised upstream image, malicious tag retargeting, or an unexpected breaking update could silently change what executes on the host.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The manifest describes a secure localhost-only deployment, but the implementation permits redirection to any host through SEARXNG_BASE_URL. This is a security-relevant integrity issue because consumers may trust the skill under a local-only threat model while the code actually allows external or internal network access beyond that promise.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The environment-based override broadens the tool’s capability from local SearXNG access to arbitrary outbound network communication. In the context of an agent skill, that makes the issue more dangerous because agent operators may grant it under the assumption that it cannot contact remote systems, while an attacker who controls environment variables can bypass that boundary.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.