Back to skill

Security audit

brave-api-free-search

Security checks for vulnerabilities and agentic risk

Overview

The skill appears intended to provide local SearXNG search, but its installer has unsafe shell execution, default persistence, and mutable external dependencies that merit review before use.

Install only if you are comfortable running a persistent local Docker service from an unpinned SearXNG image. Review or patch scripts/install.py to avoid shell=True, pin the Docker image, and consider removing --restart unless-stopped unless you want the service to survive restarts. Avoid setting SEARXNG_BASE_URL to an endpoint you do not trust, because search queries would be sent there.

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:57
Finding

Persistent Docker Container Automatically Restarts Across Sessions

Content
View full analysis

Vulnerability Details

File Location: scripts/install.py:57-63
Vulnerability Type: Persistent system 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"
    )

The persistence is also disclosed in SKILL.md:49-55:

markdown
⚠ Installation deploys a persistent Docker container (`searxng-local`) with `--restart unless-stopped`.
To remove it:

docker rm -f searxng-local

text

Technical Analysis

The installer assigns the Docker restart policy unless-stopped. Docker records this policy and automatically starts the container again when the Docker daemon or host restarts unless an administrator explicitly stops or removes it.

Although the behavior is documented, it causes code from the selected container image to execute beyond the lifetime of the installation process and across subsequent sessions. This represents system persistence. The risk is amplified by the use of an unpinned container image because the persistent component is not tied to an immutable, audited digest.

The service port is restricted to 127.0.0.1, which reduces remote network exposure but does not eliminate the persistence or supply-chain risk.

Attack Path

  1. A user runs python scripts/install.py.
  2. The installer creates the searxng-local container with --restart unless-stopped.
  3. Docker stores the restart policy.
  4. After the Docker daemon or host restarts, Docker automatically executes the container again.
  5. Any malicious or subsequently compromised code present in the container image gains recurring execution under the Docker-managed container context.

Impact Assessment

The container receives persistent execution within its Docker isolation boundary. It has ...[truncated 267 chars]

Remediation
View remediation

Remediation Suggestions

  • Remove --restart unless-stopped from the default installation.
  • If persistent operation is required, make it an explicit opt-in option and clearly explain its lifecycle and security implications before installation.
  • Use a nonpersistent default such as --restart no.
  • Pin the container to a reviewed version and immutable SHA-256 digest.
  • Provide and document an uninstall command that removes the container, associated restart policy, generated configuration, and any related resources.
  • Consider running the service with additional Docker hardening, including a read-only filesystem, dropped Linux capabilities, resource limits, and a non-root user where supported.

T08 · Insecure Dependencies

Warning
Location
scripts/install.py:57
Finding

Unpinned Python and Docker Dependencies Permit Mutable Supply-Chain Inputs

Content
View full analysis

Vulnerability Details

File Location: SKILL.md:29-35, scripts/install.py:57-63
Vulnerability Type: Unpinned third-party dependencies
Risk Level: Medium

Vulnerable Code

SKILL.md:29-35 instructs users to install an unconstrained Python package:

markdown
### Python Dependency

The runtime scripts require the Python package `requests`.

Install if missing:

pip install requests

text

scripts/install.py:57-63 starts an image without a version 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

Running pip install requests without a version constraint or package hash installs whichever release the configured package index currently resolves. Similarly, searxng/searxng has no explicit version tag or digest and therefore resolves through a mutable default tag.

This prevents reproducible installation and makes the effective runtime components capable of changing after the Skill has been audited. If an upstream account, registry, release process, package index, or mutable image tag is compromised, installation can retrieve attacker-controlled code even though the local Skill files remain unchanged.

This finding does not establish that the current requests package or SearXNG image is malicious. It identifies an unsafe dependency-resolution process that leaves installation dependent on mutable external artifacts.

Attack Path

  1. An attacker compromises an upstream release channel, registry account, package index, or mutable Docker tag.
  2. The attacker publishes a modified package or image under the dependency name expected by the project.
  3. A user follows pip install requests or runs scripts/install.py.
  4. The package manager or Docker ...[truncated 774 chars]
Remediation
View remediation

Remediation Suggestions

  • Define the Python dependency in a lock file or requirements file with an exact reviewed version.
  • Require package hashes, for example through pip's --require-hashes mode.
  • Document installation through a dedicated virtual environment rather than an unconstrained global pip install.
  • Pin SearXNG to a reviewed release and immutable image digest, for example repository:version@sha256:digest.
  • Verify image signatures or provenance where supported.
  • Add a controlled dependency-update process that reviews release notes, security advisories, checksums, and test results before changing pins.
  • Use trusted, explicitly configured package indexes and container registries.

T09 · Insecure Skill Coding Practices

Error
Location
scripts/install.py:10
Finding

Shell Command Injection Through Unquoted Project Path

Content
View full analysis

Vulnerability Details

File Location: scripts/install.py:10-13, scripts/install.py:57-63
Vulnerability Type: OS command injection
Risk Level: High

Vulnerable Code

The command helper invokes a shell:

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

The resolved settings path is then inserted into a shell command without quoting:

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 install.py. It is interpolated directly into a command string passed to subprocess.run(..., shell=True). Consequently, shell metacharacters in the project path are parsed as command syntax instead of being treated solely as part of a filename.

An attacker who can influence the directory name or extraction destination can construct a path containing shell control operators, command substitution, or redirection syntax. When a victim runs the installer, the shell evaluates that injected syntax with the victim's privileges. Ordinary whitespace in a path can also split the Docker volume argument and cause incorrect or failed execution.

The hardcoded shell commands elsewhere in the installer do not receive direct user input, but using shell=True unnecessarily increases the attack surface.

Attack Path

  1. An attacker distributes or places the project in a directory whose name contains shell metacharacters and an injected command.
  2. The victim runs python scripts/install.py from that project.
  3. Path(__file__).resolve() incorporates the malicious directory name into SETTINGS_FILE.
  4. The installer concatenates that path into the docker run comm ...[truncated 868 chars]
Remediation
View remediation

Remediation Suggestions

  • Eliminate shell=True and pass each command as an argument list.
  • Pass the complete volume mapping as one argument so paths containing whitespace or metacharacters remain data rather than shell syntax.
  • Use check=True for explicit error handling.
  • Replace shell redirection with stdout=subprocess.DEVNULL and stderr=subprocess.DEVNULL.
  • Replace || true with Python-side return-code handling.

A safer pattern is:

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:REVIEWED_DIGEST",
    ],
    check=True,
)

Apply the same no-shell approach to Docker detection and container removal.

Vulnerability Patterns
  • 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
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (16)

Tainted flow: 'BASE_URL' from os.environ.get (line 7, credential/environment) → requests.get (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 · 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
90% confidence
Finding

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

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

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

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

If the service endpoint can be redirected through SEARXNG_BASE_URL, the 'localhost-only' claim is materially false and may cause users to trust a component that can communicate with arbitrary remote systems. Combined with the lack of evidence for Brave API compatibility or hidden dev-flag support, this is a deceptive description-to-behavior mismatch that weakens informed consent and can expand network attack surface.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

If the service endpoint can be redirected through SEARXNG_BASE_URL, the 'localhost-only' claim is materially false and may cause users to trust a component that can communicate with arbitrary remote systems. Combined with the lack of evidence for Brave API compatibility or hidden dev-flag support, this is a deceptive description-to-behavior mismatch that weakens informed consent and can expand network attack surface.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding

If the service endpoint can be redirected through SEARXNG_BASE_URL, the 'localhost-only' claim is materially false and may cause users to trust a component that can communicate with arbitrary remote systems. Combined with the lack of evidence for Brave API compatibility or hidden dev-flag support, this is a deceptive description-to-behavior mismatch that weakens informed consent and can expand network attack surface.

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

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding

The skill advertises installation and runtime behavior that requires powerful capabilities such as shell, network, file writes, and environment access, but it does not declare any explicit tool scope or permissions boundary. This creates a transparency and containment problem: users or orchestrators may grant broader execution than expected, increasing the chance of unsafe command execution, persistent container deployment, or unintended network access.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The script executes shell commands via subprocess with shell=True and later uses it to remove an existing Docker container and start a new one. While there is a print for missing Docker and a success message, there is no prior disclosure, confirmation, or explanatory comment warning the user that installation will run shell commands and forcibly delete any existing 'searxng-local' container.

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

The hidden --dev flag silently disables or weakens protections by setting safe_search to 0 and limiter.enabled to false, contradicting the secure deployment messaging. Hidden behavior that reduces safeguards increases the chance of unsafe operation and makes operators less able to make informed security decisions.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The manifest description emphasizes a secure localhost-only deployment, but the installer writes a SearXNG configuration with server.bind_address: 0.0.0.0, which directs the service to listen on all interfaces in the container. Although Docker publishing is limited to 127.0.0.1 on the host, the explicit all-interface bind conflicts with the narrower localhost-only claim in the skill description.

Content

No source excerpt is available for this finding.

Rp1

Medium
Category
MCP Rug Pull
Confidence
94% confidence
Finding

The installer pulls and runs searxng/searxng without a fixed tag or digest, so executions are non-reproducible and trust whatever image the registry serves at install time. If the upstream image is replaced, compromised, or unexpectedly changed, users may run altered code with network and filesystem access.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
86% confidence
Finding

The script creates the configuration directory and writes settings.yml, which affects local filesystem state, but provides no user-facing notice that a file will be created or overwritten. There is also no comment or docstring documenting this behavior in the code shown.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.