Summary
SearXNG Basic Authentication Credentials Exposed Through MCP Logs and JSON-RPC Error Responses
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 vulnerabilities
All Supply chain →- MEDIUMCVE-2026-55102
hashi-vault-js: Vault token and secret values exposed in thrown errors
- MEDIUMCVE-2026-73555
vLLM: Unauthenticated Internal Path and Username Disclosure via Validation Error Messages
- HIGHCVE-2026-61798
netty-incubator-codec-ohttp: BoringSSL HPKE private key bytes exposed through toString() and exception messages
- HIGHGHSA-ghvf-qf6h-g8x5
NocoBase: Arbitrary File Write chained with Local file Inclusion leads to Remote code execution
- HIGHGHSA-p77j-g7h5-r2vw
GeoLens's authorization and cache-scope flaws disclose private dataset data and metadata to unauthorized users (fixed in 1.2.4)
- MEDIUMGHSA-92hr-gmr6-h8cp
Etherpad addressed weak token RNG, login timing, plugin path handling, API request handling