Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 25 additions & 4 deletions packages/mcp-server/src/code-tool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,22 +179,43 @@ const localDenoHandler = async ({

const allowRead = allowReadPaths.join(',');

// Never forward the host process.env into the sandbox: on a deployed server it holds
// secrets (OAuth client secret, Redis URL, API keys) and executed code can read its own
// environment. The worker receives SDK credentials explicitly via the request body, so the
// subprocess only needs a minimal set of system vars plus the per-request upstream envs.
const SUBPROCESS_ENV_ALLOWLIST = [
'PATH',
'HOME',
'TMPDIR',
'TMP',
'TEMP',
'LANG',
'LC_ALL',
'DENO_DIR',
'DENO_INSTALL_ROOT',
];
const subprocessEnv: Record<string, string> = {};
for (const key of SUBPROCESS_ENV_ALLOWLIST) {
const value = process.env[key];
if (value !== undefined) subprocessEnv[key] = value;
}
Object.assign(subprocessEnv, reqContext.upstreamClientEnvs);

const worker = await newDenoHTTPWorker(url.pathToFileURL(workerPath), {
denoExecutable: denoPath,
runFlags: [
`--node-modules-dir=manual`,
`--allow-read=${allowRead}`,
`--allow-net=${baseURLHostname}`,
// Allow environment variables because instantiating the client will try to read from them,
// even though they are not set.
// even though they are not set. The subprocess env is curated above, so no host secrets
// are exposed even though read access is granted.
'--allow-env',
],
printOutput: true,
spawnOptions: {
cwd: path.dirname(workerPath),
// Merge any upstream client envs into the Deno subprocess environment,
// with the upstream env vars taking precedence.
env: { ...process.env, ...reqContext.upstreamClientEnvs },
env: subprocessEnv,
},
});

Expand Down
Loading
Loading