diff --git a/.changeset/fix-mcp-config-shell-injection.md b/.changeset/fix-mcp-config-shell-injection.md new file mode 100644 index 0000000000..376b96f67d --- /dev/null +++ b/.changeset/fix-mcp-config-shell-injection.md @@ -0,0 +1,5 @@ +--- +"e2b": patch +--- + +fix(sdk): prevent shell injection in MCP config by using proper shell escaping (shlex.quote in Python, shellQuote helper in JS/TS) diff --git a/packages/js-sdk/src/sandbox/index.ts b/packages/js-sdk/src/sandbox/index.ts index 7384dc7af2..29492a9f1e 100644 --- a/packages/js-sdk/src/sandbox/index.ts +++ b/packages/js-sdk/src/sandbox/index.ts @@ -30,6 +30,14 @@ import { compareVersions } from 'compare-versions' import { SandboxError } from '../errors' import { ENVD_DEBUG_FALLBACK, ENVD_DEFAULT_USER } from '../envd/versions' +/** + * Escape a string for safe inclusion in a single-quoted shell argument. + * Equivalent to Python's shlex.quote(). + */ +function shellQuote(s: string): string { + return "'" + s.replace(/'/g, "'\\''") + "'" +} + /** * Options for sandbox upload/download URL generation. */ @@ -299,7 +307,7 @@ export class Sandbox extends SandboxApi { if (sandboxOpts?.mcp) { sandbox.mcpToken = crypto.randomUUID() const res = await sandbox.commands.run( - `mcp-gateway --config '${JSON.stringify(sandboxOpts?.mcp)}'`, + `mcp-gateway --config ${shellQuote(JSON.stringify(sandboxOpts.mcp))}`, { user: 'root', envs: { @@ -394,7 +402,7 @@ export class Sandbox extends SandboxApi { if (sandboxOpts?.mcp) { sandbox.mcpToken = crypto.randomUUID() const res = await sandbox.commands.run( - `mcp-gateway --config '${JSON.stringify(sandboxOpts?.mcp)}'`, + `mcp-gateway --config ${shellQuote(JSON.stringify(sandboxOpts.mcp))}`, { user: 'root', envs: { diff --git a/packages/python-sdk/e2b/sandbox_async/main.py b/packages/python-sdk/e2b/sandbox_async/main.py index 0fe175de3d..3dd7044ea7 100644 --- a/packages/python-sdk/e2b/sandbox_async/main.py +++ b/packages/python-sdk/e2b/sandbox_async/main.py @@ -1,6 +1,7 @@ import datetime import json import logging +import shlex import uuid from typing import Dict, List, Optional, Union, overload @@ -235,7 +236,7 @@ async def create( sandbox._mcp_token = token res = await sandbox.commands.run( - f"mcp-gateway --config '{json.dumps(mcp)}'", + f"mcp-gateway --config {shlex.quote(json.dumps(mcp))}", user="root", envs={"GATEWAY_ACCESS_TOKEN": token}, ) @@ -616,7 +617,7 @@ async def beta_create( sandbox._mcp_token = token res = await sandbox.commands.run( - f"mcp-gateway --config '{json.dumps(mcp)}'", + f"mcp-gateway --config {shlex.quote(json.dumps(mcp))}", user="root", envs={"GATEWAY_ACCESS_TOKEN": token}, ) diff --git a/packages/python-sdk/e2b/sandbox_sync/main.py b/packages/python-sdk/e2b/sandbox_sync/main.py index a52cb4c303..43f3a858c5 100644 --- a/packages/python-sdk/e2b/sandbox_sync/main.py +++ b/packages/python-sdk/e2b/sandbox_sync/main.py @@ -1,6 +1,7 @@ import datetime import json import logging +import shlex import uuid from typing import Dict, List, Optional, Union, overload @@ -233,7 +234,7 @@ def create( sandbox._mcp_token = token res = sandbox.commands.run( - f"mcp-gateway --config '{json.dumps(mcp)}'", + f"mcp-gateway --config {shlex.quote(json.dumps(mcp))}", user="root", envs={"GATEWAY_ACCESS_TOKEN": token}, ) @@ -617,7 +618,7 @@ def beta_create( sandbox._mcp_token = token res = sandbox.commands.run( - f"mcp-gateway --config '{json.dumps(mcp)}'", + f"mcp-gateway --config {shlex.quote(json.dumps(mcp))}", user="root", envs={"GATEWAY_ACCESS_TOKEN": token}, )