Skip to content

mcp: server error text can write config secrets into the local engine log #53051

Open

Description

@Skeptomenos

Reproduced with opencode v2.0.20 (commit 84c9be93a56304a108f1a22df0c5d62c26d5b6ca). The same two warning sites are still present at v2.0.22: packages/core/src/mcp/client.ts:293 and packages/core/src/mcp/index.ts:476. All values below are fake.

An MCP server can echo a configured environment value in a JSON-RPC error. The engine writes that error text to its own local opencode.log. The tools-list failure and the subsequent connection failure each retain the value. A buffered log may look clean until the engine shuts down and flushes it.

Create echo-error.mjs in an empty test directory:

import readline from "node:readline";
for await (const line of readline.createInterface({ input: process.stdin })) {
  const request = JSON.parse(line);
  if (request.id === undefined) continue;
  const reply = request.method === "initialize"
    ? { result: {
        protocolVersion: request.params.protocolVersion,
        capabilities: { tools: {} },
        serverInfo: { name: "echo-error", version: "0" },
      } }
    : { error: { code: -32603, message: process.env.FAKE_SECRET } };
  process.stdout.write(JSON.stringify({ jsonrpc: "2.0", id: request.id, ...reply }) + "\n");
}

Use this opencode.json in that directory (Node must be available to the engine):

{
  "mcp": {
    "servers": {
      "echo-error": {
        "type": "local",
        "command": ["node", "echo-error.mjs"],
        "environment": { "FAKE_SECRET": "FAKE_MCP_LOG_SECRET_ONLY" },
        "codemode": false
      }
    }
  }
}
  1. Start the pinned engine from this directory. Use an isolated test HOME and XDG data/config directories, with no credentials or personal config.
  2. Let MCP startup fail at tools/list (a native MCP list read can confirm the failed status).
  3. Stop the engine cleanly so its buffered file log flushes. Inspect opencode/log/opencode.log under the isolated XDG data directory.

Observed: the exact fake value appears in two WARN entries: failed to list MCP tools (error) and mcp connect failed (status.error). Engine stdout/stderr remained clean in the reproduction. Expected: the engine's local log does not retain secret-bearing server error text.

Suggested fix: treat MCP error text as untrusted diagnostic data. Log a fixed message plus safe server/status/error-code metadata at both warning sites, or redact sensitive text before those fields reach the logger. A regression should make a stdio server echo a fake config value, stop/flush the engine and assert the value is absent from the local log. Checking stdout or the file before flush misses this case.

Source: tools-list warning, connection warning.

Activity

  1. Skeptomenos commented on Oct 4, 2026

    @Skeptomenos
    Author

    A second case with the same root cause: model provider sign-in.

    When a provider sign-in fails, the provider plugin's error carries text from the provider's response. That error text reaches the engine's local opencode.log the same way. Examples at v2.0.22:

    • packages/core/src/plugin/provider/xai.ts:161-164: a token-exchange failure includes the provider's error_description.
    • packages/core/src/plugin/provider/xai.ts:173-176: a failed request includes the full upstream response body.

    The stored attempt also returns that message (packages/core/src/integration.ts:757-758).

    Reproduced with a synthetic provider whose sign-in start fails with a fake value in its error text. After a clean stop that flushes the log, the value appears in the local opencode.log.

    Suggested fix: the same as above. Treat provider and integration error text as untrusted diagnostic data: log a fixed message with safe metadata (provider id, method id, status code), and keep upstream bodies out of the log and out of the stored attempt message. A regression test would plant a fake value in a provider error and assert it is absent from the flushed log.

  2. opencodejr commented on Oct 27, 2026

    @opencodejr
    Contributor

    Thanks for the detailed write-up and the minimal repro, @Skeptomenos.

    The request: when a stdio or remote MCP server echoes a configured value (for example an environment entry) in its JSON-RPC error, OpenCode shouldn't write it to the local opencode.log. You suggest either a fixed message with safe metadata (server, status, error code) or redacting known config values at the failed to list MCP tools and mcp connect failed warning sites. Both sites are still present on the current main line.

    Right now the engine logs server error text as it arrives, and there's no general log-redaction mechanism, so logging it is the intended behaviour. That's why I'm tracking this as a hardening improvement rather than a bug. Maintainers will consider it. A plugin can't do this today, because plugins have no hook into the engine's own file log.

    Related issues:

  3. impharaon commented on Nov 12, 2026

    @impharaon

    The same text also reaches the server's failed status (Mcp.Status.Failed.error), which the API returns and clients display. It isn't limited to the log:

    • start in packages/core/src/mcp/connection.ts builds the status from error.message. For a remote server that answers a POST with an error page, the MCP SDK's message is Error POSTing to endpoint: <response body>.
    • For a local server that exits during the handshake, packages/core/src/mcp/stdio.ts appends up to the last 1,000 characters of stderr (STDERR_TAIL). That text reaches the status through lastError/explain in packages/core/src/mcp/client.ts.

    In our embedding app, the status and the warning logs say only HTTP <status> or MCP server process exited with code N. Stderr still goes to the debug log as it does today. A fix in the describe/explain helpers in client.ts would cover both the log sites named here and the status.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions