Topic: mcp server object injection security

MCP server object injection security — unsafe deserialization and eval patterns

Object injection covers a class of vulnerabilities where attacker-controlled data is interpreted as executable code or as a live object with callable methods. In an MCP server, the attacker-controlled data is the AI model's tool argument values. If a tool handler passes those values to eval(), the Function() constructor, node-serialize, Python's pickle, or similar mechanisms that bridge data and code, the result is remote code execution in the server process — with whatever system access the server process holds.

The three most common object injection patterns in MCP servers

Pattern 1: eval() on tool arguments

// VULNERABLE: eval() on AI-supplied expression argument
server.tool('calculate', z.object({
  expression: z.string(),
}), async ({ expression }) => {
  // If expression = "require('child_process').execSync('cat /etc/passwd')"
  // this executes arbitrary code in the server process
  const result = eval(expression)  // CRITICAL: eval of AI-supplied string
  return { result: String(result) }
})

// ALSO VULNERABLE: new Function() constructor
const fn = new Function('x', `return ${expression}`)  // equivalent to eval

// SAFE ALTERNATIVE: use a math parser library for expression evaluation
import { evaluate } from 'mathjs'
server.tool('calculate', z.object({
  expression: z.string().max(500),
}), async ({ expression }) => {
  try {
    const result = evaluate(expression)  // math-only, no code execution
    if (typeof result !== 'number') throw new Error('Non-numeric result')
    return { result }
  } catch (e) {
    return { error: 'Invalid expression' }
  }
})

Pattern 2: node-serialize IIFE exploitation

// VULNERABLE: node-serialize.unserialize() on AI-supplied data
import serialize from 'node-serialize'

server.tool('loadConfig', z.object({
  serializedConfig: z.string(),
}), async ({ serializedConfig }) => {
  // Malicious payload: {"rce":"_$$ND_FUNC$$_function(){require('child_process').execSync('id')}()"}
  // node-serialize evaluates _$$ND_FUNC$$_ strings as JavaScript functions
  // and if they are IIFEs (ending in ()), executes them immediately on unserialize
  const config = serialize.unserialize(serializedConfig)  // CRITICAL: RCE
  return applyConfig(config)
})

// SAFE ALTERNATIVE: use JSON.parse() for data interchange
server.tool('loadConfig', z.object({
  config: configSchema,  // validated Zod schema, not raw serialized string
}), async ({ config }) => {
  return applyConfig(config)
})

// If you must accept a serialized string, use JSON.parse with schema validation:
server.tool('loadConfig', z.object({
  configJson: z.string().max(10000),
}), async ({ configJson }) => {
  let parsed: unknown
  try {
    parsed = JSON.parse(configJson)
  } catch {
    return { error: 'Invalid JSON' }
  }
  const result = configSchema.safeParse(parsed)
  if (!result.success) return { error: 'Invalid config structure' }
  return applyConfig(result.data)
})

Pattern 3: Python pickle deserialization

# VULNERABLE: pickle.loads() on AI-supplied base64 data
import pickle, base64

@server.tool()
async def load_model(model_data: str) -> dict:
    # If model_data contains a pickle payload that sets __reduce__,
    # pickle.loads() will execute arbitrary Python code
    data = base64.b64decode(model_data)
    model = pickle.loads(data)  # CRITICAL: arbitrary code execution
    return {"loaded": True}

# SAFE ALTERNATIVE: use JSON or MessagePack for data exchange
import json
from pydantic import BaseModel

class ModelConfig(BaseModel):
    weights_url: str
    architecture: str
    version: str

@server.tool()
async def load_model(config_json: str) -> dict:
    try:
        raw = json.loads(config_json)
        config = ModelConfig(**raw)
    except (json.JSONDecodeError, ValueError) as e:
        return {"error": "Invalid config"}
    # Load model from validated URL, not from deserialized binary
    model = await fetch_model_from_url(config.weights_url)
    return {"loaded": True, "version": config.version}

What SkillAudit checks

See also

Check your MCP server for eval, object injection, and unsafe deserialization patterns.

Run a free audit → How grading works →