Skip to content

fix: isolate stdio MCP child env from OpenWiki credentials#287

Open
Greg Land (bikeusaland) wants to merge 4 commits into
langchain-ai:mainfrom
bikeusaland:security/mcp-child-env-isolation
Open

fix: isolate stdio MCP child env from OpenWiki credentials#287
Greg Land (bikeusaland) wants to merge 4 commits into
langchain-ai:mainfrom
bikeusaland:security/mcp-child-env-isolation

Conversation

@bikeusaland

@bikeusaland Greg Land (bikeusaland) commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Problem

Stdio MCP subprocesses were spawned with the full process.env:

const child = spawn(transport.command, transport.args ?? [], {
  env: { ...process.env, ...resolveChildEnv(transport.env ?? {}) },
  ...
});

Because the MCP server command comes from user-editable connector config, any MCP server binary inherited the user's entire credential set — every provider API key (Anthropic, OpenAI, Tavily, …) and OAuth refresh token (Gmail, Slack, X, …) sitting in process.env. A malicious or compromised MCP server got all of them for free.

Fix

Introduce buildChildEnv, which constructs the child environment from:

  1. An allow-list of safe base variables an MCP process may legitimately need — PATH, HOME/USERPROFILE, TMPDIR/TEMP/TMP, locale/terminal vars, and Windows equivalents (SystemRoot, ComSpec, PATHEXT).
  2. Only the credentials the transport explicitly declares via transport.env (resolved through the existing resolveChildEnv validation/resolution path).

The full process.env is never forwarded. All three stdio spawn sites (executeStdioMcp, executeStdioMcpTool, listStdioMcpTools) now route through buildChildEnv.

Behavior change

If an MCP server previously relied on a variable that is neither allow-listed nor declared in its transport.env, it will no longer see it — that variable must now be listed explicitly in the connector's transport.env. This is the intended least-privilege behavior.

Tests

Adds test/mcp-client.test.ts (the module had no tests) pinning the invariant:

  • OpenWiki credentials don't leak into the child env by key or by value
  • allow-listed base vars (e.g. PATH) pass through
  • declared refs resolve to the mapped target var only
  • invalid / unresolvable refs throw

Full gate passes locally: typecheck, lint:check, format:check, and the test suite (200 tests, +5 new).

🤖 Generated with Claude Code

Related PRs

Part of a batch from a single codebase-review pass. Independent, main-based branches that can merge in any order.

Security

Correctness

Feature

(This PR is #287.)

Stdio MCP subprocesses were spawned with the full process.env, so any
MCP server command (which comes from user-editable connector config)
inherited every provider API key and OAuth refresh token.

Build the child env from an allow-list of safe base variables (PATH,
HOME, TMPDIR, locale/terminal, Windows equivalents) plus only the
credentials the transport explicitly declares via transport.env. The
full process.env is never forwarded. Route all three stdio spawn sites
through the new buildChildEnv helper.

Add test/mcp-client.test.ts pinning the invariant: secrets don't leak by
key or value, allow-listed base vars pass through, declared refs resolve
to the mapped target var, and invalid/unresolvable refs throw.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
audichuang added a commit to audichuang/openwiki that referenced this pull request Jul 15, 2026
…angchain-ai#287)

Spawned stdio MCP servers inherited the full process.env — every provider
API key and OAuth refresh token. Build the child env from a small allowlist
of safe base vars (PATH/HOME/tmp/locale/Windows) plus only the credentials
the transport declares via transport.env. Ported from upstream PR langchain-ai#287
(clean, engine-agnostic; our tree did not touch mcp-client.ts).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
audichuang added a commit to audichuang/openwiki that referenced this pull request Jul 16, 2026
…angchain-ai#287)

Spawned stdio MCP servers inherited the full process.env — every provider
API key and OAuth refresh token. Build the child env from a small allowlist
of safe base vars (PATH/HOME/tmp/locale/Windows) plus only the credentials
the transport declares via transport.env. Ported from upstream PR langchain-ai#287
(clean, engine-agnostic; our tree did not touch mcp-client.ts).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant