[BUG] MCPB: "sensitive" user_config injected as __encrypted__: ciphertext instead of decrypted plaintext (Windows)

Status Fixed / completed
Maintainer reply None cached
Activity 0 comments · opened Jul 16, 2026 · closed Aug 25, 2026

Preflight

  • Searched existing open bug issues (user_config, __encrypted__, sensitive) — no match. Related but distinct: #39125 (cowork VM injects no user_config env vars — env absent, not undecrypted; closed not-planned) and #70397 (manifest nameserverInfo.name mismatch — tools/call never reaches the server). In this report the env vars are present and tools/call does reach the server.
  • Single bug report.

What's Wrong?

For an installed .mcpb desktop extension whose user_config has a field marked "sensitive": true, Claude Desktop injects that field into the server's launch environment as its stored __encrypted__:… ciphertext instead of the decrypted plaintext. Non-sensitive fields in the same user_config are resolved correctly.

The extension therefore starts, handshakes, and lists tools fine — but the first tool call that actually uses the secret fails, because the server receives a garbage value where the secret should be.

Concretely: our server takes a .pfx client-certificate password via a sensitive field. The server receives RAVENDB_CERTIFICATE_PASSWORD=__encrypted__:djEw… and the .NET PKCS#12 loader throws CryptographicException: The certificate data cannot be read with the provided password. Every tool that touches the backend then returns the opaque MCP error An error occurred invoking '<tool>'. initialize/tools/list succeed because they never read the secret.

Verified the value is the problem, not the transport: substituting the plaintext password for the same field (same cert, same cluster) makes every tool work immediately.

What Should Happen?

A "sensitive": true user_config value should be decrypted before ${user_config.*} substitution, so the server process receives the plaintext the user entered — the same as non-sensitive fields. Per the MCPB docs, sensitive values are stored in the OS keychain and are expected to be provided to the server decrypted.

Error Messages/Logs

# Env actually received by the extension's server process (read from the live
# child process; non-sensitive fields correct, the sensitive one is ciphertext):
RAVENDB_URLS=https://localhost:7072                         # correct
RAVENDB_CERTIFICATE_PATH=...\server.pfx                     # correct
RAVENDB_CERTIFICATE_PASSWORD=__encrypted__:djEwkOqh…        # <-- undecrypted ciphertext

# Resulting server-side exception on the first tool call that opens the cert:
"list_databases" threw an unhandled exception.
System.Security.Cryptography.CryptographicException:
    The certificate data cannot be read with the provided password, the password may be incorrect.
    at System.Security.Cryptography.X509Certificates.X509CertificateLoader.LoadPkcs12(...)

# What the model/client sees (opaque):
{"result":{"content":[{"type":"text","text":"An error occurred invoking 'list_databases'."}],"isError":true}}

# Same field with PLAINTEXT instead of the ciphertext -> works:
"list_databases" completed. IsError = False.
{"databases":["Demo"]}

Steps to Reproduce

  1. Build an .mcpb extension whose user_config includes a "sensitive": true field wired into server.mcp_config.env, e.g.:

``json
"server": { "mcp_config": { "env": {
"MY_SECRET": "${user_config.MY_SECRET}"
} } },
"user_config": {
"MY_SECRET": { "type": "string", "sensitive": true, "required": false }
}
``

  1. Install + enable it, and enter a value for MY_SECRET in the extension's settings UI (Claude Desktop stores it as __encrypted__:…).
  2. Have the server log or echo the received env var (or read the live process env).
  3. Invoke a tool that uses the secret.
  4. Observed: the server receives MY_SECRET=__encrypted__:… (the ciphertext), not the plaintext entered in the UI. Any logic using the secret fails. Non-sensitive fields in the same user_config resolve correctly.

Minimal check without a full server: any extension that logs process.env.MY_SECRET at startup shows the __encrypted__: prefix.

Claude Model

Opus

Is this a regression?

I don't know

Claude Code Version

Claude Desktop 1.21459.3 (Windows MSIX). Failure observed via the Desktop-integrated Claude Code / agent session (MCP client id local-agent-mode-…); the same undecrypted value was also present in regular-Chat–spawned launches (client id claude-ai).

Platform

Anthropic API

Operating System

Windows

Terminal/Shell

Other

Additional Information

  • Root cause (isolated): it is specifically the sensitive flag → encryption path. Plaintext in the same field works; the __encrypted__: value fails. Non-sensitive fields are fine, so ${user_config.*} substitution itself works — only the decrypt-before-inject step for sensitive fields is missing on this launch path.
  • How the value was confirmed: read directly from the running server process's environment block, so this is the literal string the process received, not an inference.
  • Not #70397 / #39125: here tools/call does reach the server (the server throws), and the non-sensitive env vars are present — the two failure modes those issues describe don't apply.
  • Workaround (for extension authors): don't mark the field sensitive, or accept an unencrypted credential — both let the plaintext through — until decryption on this path is fixed.
  • Suggested fixes: (1) decrypt sensitive user_config values before ${user_config.*} substitution on every launch path (Chat and agent/cowork); at minimum (2) never forward a value still carrying the __encrypted__: marker — fail loudly instead of injecting ciphertext.

View original on GitHub ↗