medium

GHSA-hjwh-xvfw-qrwj

npm · mcp-searxng

Summary

SearXNG Basic Authentication Credentials Exposed Through MCP Logs and JSON-RPC Error Responses

Severity
medium
CVSS
5.5
CWE
CWE-209, CWE-532
Published
2026-08-19
Updated
2026-08-19

Advisory details

Summary

mcp-searxng version 1.11.0 exposes SearXNG Basic Authentication credentials embedded in the SEARXNG_URL environment variable.

When the server starts in STDIO mode and an MCP client connects, the complete SEARXNG_URL, including its username and password, is sent to the client through an MCP notifications/message logging notification.

Additionally, when URL validation fails, the complete credential-bearing URL is included in the configuration error. This error is logged through MCP and returned to the client as a JSON-RPC error response.

For example, a value such as:

http://username:password@searxng.example.com

is exposed without redaction.

A connected MCP client or anyone with access to captured server logs may recover the SearXNG credentials and use them to access the configured SearXNG instance.

The issue was confirmed in:

mcp-searxng 1.11.0

Suggested severity: Medium

Details

mcp-searxng supports SearXNG Basic Authentication by embedding credentials in the URL userinfo component:

https://username:password@searxng.example.com

The project contains a redaction function named redactSearxngInstanceUrl(), but it is not used in several logging and error-handling paths.

Startup console disclosure

In src/index.ts:373-378, the server retrieves the raw SearXNG URLs and writes them directly to stderr:

const searxngInstances = getSearxngInstances();

if (searxngInstances.length > 0) {
  console.error(`🌐 SearXNG URLs: ${searxngInstances.join("; ")}`);
}

getSearxngInstances() returns the unmodified environment-variable values.

Relevant code in src/searxng-instances.ts:25-38:

export function parseSearxngUrls(
  raw: string | undefined = process.env.SEARXNG_URL
): string[] {
  if (raw === undefined) {
    return [];
  }

  return raw
    .split(";")
    .map((entry) => entry.trim())
    .filter((entry) => entry !== "");
}

export function getSearxngInstances(): string[] {
  return parseSearxngUrls();
}

MCP logging notification disclosure

After the MCP client connects, src/index.ts:388-393 sends the complete URL through the MCP logging interface:

const searxngInstances = getSearxngInstances();

logMessage(
  mcpServer,
  "info",
  `SearXNG URLs: ${
    searxngInstances.length > 0
      ? searxngInstances.join("; ")
      : "not configured"
  }`
);

logMessage() passes this value to sendLoggingMessage() in src/logging.ts:15-25:

mcpServer.sendLoggingMessage({
  level,
  data: notificationData
});

As a result, the connected MCP client receives a message containing the username and password:

{
  "method": "notifications/message",
  "params": {
    "level": "info",
    "data": {
      "message": "SearXNG URLs: http://username:password@searxng.example.com"
    }
  },
  "jsonrpc": "2.0"
}

Configuration error disclosure

The URL validation function includes the complete unredacted value in error messages.

Relevant code in src/searxng-instances.ts:44-52:

export function validateSearxngInstanceUrl(
  value: string
): string | null {
  try {
    const url = new URL(value);

    if (!["http:", "https:"].includes(url.protocol)) {
      return `SEARXNG_URL invalid protocol for "${value}": ${url.protocol}`;
    }
  } catch {
    return `SEARXNG_URL invalid format: ${value}`;
  }

  return null;
}

The validation error is aggregated by validateEnvironment() in src/error-handler.ts:175-203:

const validationError =
  validateSearxngInstanceUrl(searxngUrl);

if (validationError) {
  issues.push(validationError);
}

The complete error is then thrown from src/search.ts:689-693:

const validationError = validateEnvironment();

if (validationError) {
  logMessage(mcpServer, "error", "Configuration invalid");
  throw new MCPSearXNGError(validationError);
}

The tool handler in src/index.ts:254-260 sends the error message and stack trace through MCP logging, then rethrows it:

logMessage(
  mcpServer,
  "error",
  `Tool execution error: ${
    error instanceof Error
      ? error.message
      : String(error)
  }`,
  {
    tool: name,
    args: args,
    error:
      error instanceof Error
        ? error.stack
        : String(error)
  }
);

throw error;

Rethrowing the error causes the same unredacted credential-bearing URL to be returned in the JSON-RPC error response.

Existing redaction function is not used

The project already contains a suitable redaction function in src/searxng-instances.ts:57-69:

export function redactSearxngInstanceUrl(
  raw: string
): string {
  try {
    const url = new URL(raw);

    if (!url.username && !url.password) {
      return raw;
    }

    url.username = "";
    url.password = "";
    return url.toString();
  } catch {
    return raw.replace(
      /^([a-zA-Z][a-zA-Z0-9+.-]*:\/\/)[^/]*@/,
      "$1"
    );
  }
}

However, this function is not applied before startup logging, MCP logging, or configuration error construction.

The MCP manifest also marks SEARXNG_URL as non-secret in .mcp/server.json:20-25:

{
  "name": "SEARXNG_URL",
  "description": "URL of your SearXNG instance",
  "isRequired": true,
  "isSecret": false,
  "format": "string"
}

Because credentials may be embedded in this variable, it should be classified as a secret.

PoC

The following proof of concept uses fake credentials. A real SearXNG server is not required.

Requirements

Node.js 20 or newer
npm
mcp-searxng 1.11.0 source code

Build the application

unzip mcp-searxng-main.zip
cd mcp-searxng-main

npm ci
npm run build

Test 1: Credential disclosure through MCP logging

Create an MCP initialization request:

cat > /tmp/mcp-init.jsonl <<'EOF'
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"credential-leak-poc","version":"1.0.0"}}}
EOF

Start the server with fake credentials embedded in a valid HTTP URL:

SEARXNG_URL='http://MCP_POC_USER_7391:MCP_POC_PASS_7391@127.0.0.1:9' \
timeout 8s node dist/cli.js \
< /tmp/mcp-init.jsonl \
2>&1 | tee credential-log-leak.txt

Search the output for the credentials:

grep -nE \
'MCP_POC_USER_7391|MCP_POC_PASS_7391' \
credential-log-leak.txt

Observed result

The complete credential-bearing URL is exposed:

SearXNG URLs: http://MCP_POC_USER_7391:MCP_POC_PASS_7391@127.0.0.1:9

It is also delivered to the MCP client:

{
  "method": "notifications/message",
  "params": {
    "level": "info",
    "data": {
      "message": "SearXNG URLs: http://MCP_POC_USER_7391:MCP_POC_PASS_7391@127.0.0.1:9"
    }
  },
  "jsonrpc": "2.0"
}

This confirms that a connected MCP client can recover the configured username and password without accessing the host environment.

Test 2: Credential disclosure through JSON-RPC errors

Create initialization and tool-call requests:

cat > /tmp/mcp-error-poc.jsonl <<'EOF'
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"credential-error-poc","version":"1.0.0"}}}
{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"searxng_web_search","arguments":{"query":"credential leak test"}}}
EOF

Start the server with a credential-bearing URL that uses an unsupported protocol:

SEARXNG_URL='ftp://MCP_POC_USER_7391:MCP_POC_PASS_7391@example.invalid' \
timeout 8s node dist/cli.js \
< /tmp/mcp-error-poc.jsonl \
2>&1 | tee credential-error-leak.txt

Search the response:

grep -nE \
'MCP_POC_USER_7391|MCP_POC_PASS_7391' \
credential-error-leak.txt

Observed result

The complete URL is exposed in the MCP logging notification:

Tool execution error: Configuration Issues: SEARXNG_URL invalid protoco

References

Related advisories

Is your project exposed to this? Stateward checks every dependency on every pull request and flags it only if your code actually reaches it.

Check my repo

Summarize with AI

ChatGPTClaudePerplexity

Sources: CISA KEV (public domain), OSV.dev & GitHub Advisory Database (CC-BY-4.0), FIRST EPSS, NVD/CWE (public domain). Served live from the Stateward advisory database.