[BUG] MCPB: "sensitive" user_config injected as __encrypted__: ciphertext instead of decrypted plaintext (Windows)
Preflight
- Searched existing open
bugissues (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 (manifestname↔serverInfo.namemismatch —tools/callnever reaches the server). In this report the env vars are present andtools/calldoes 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
- Build an
.mcpbextension whoseuser_configincludes a"sensitive": truefield wired intoserver.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 }
}
- Install + enable it, and enter a value for
MY_SECRETin the extension's settings UI (Claude Desktop stores it as__encrypted__:…). - Have the server log or echo the received env var (or read the live process env).
- Invoke a tool that uses the secret.
- 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 sameuser_configresolve 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
sensitiveflag → 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/calldoes 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
sensitiveuser_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.