Спецификация Model Context Protocol (MCP) превратила языковые модели из изолированных генераторов текста в автономные агентные системы, наделенные возможностью исполнять действия в реальном мире. Посредством MCP-инструментов (Tools) агент получает доступ к вызову shell-команд, чтению и записи в файловую систему, выполнению SQL-запросов к СУБД и взаимодействию с внешними REST API.
Однако наделение агента правами исполнения операций открывает критическую поверхность атаки. Если модель подвергается прямой или косвенной инъекции (Prompt Injection), любой доступный MCP-инструмент немедленно становится вектором удаленного выполнения кода (Remote Code Execution, RCE), несанкционированного доступа к данным (SSRF) или горизонтальной эскалации привилегий (Privilege Escalation).
Открытый стандарт протокола клиент-серверного взаимодействия, позволяющий подключать к LLM контекстные ресурсы (Resources), исполняемые инструменты (Tools) и шаблоны диалогов (Prompts). Класс уязвимостей, позволяющий коду внутри контейнера преодолеть границы изоляции пространства имен (Namespaces) и контрольных групп (cgroups) и получить доступ к ядру хост-системы.Классический Docker с рантаймом по умолчанию (runc) не обеспечивает достаточной изоляции для исполнения произвольного недоверенного кода, сгенерированного агентом: общее ядро Linux (Shared Kernel) подвержено атакам эксплуатации системных вызовов (Syscall Vulnerabilities). Инженерная безопасность MCP в 2026 году базируется на трехуровневом изолированном стеке: контейнеризации gVisor (runsc), мгновенных песочницах WebAssembly (WASI) и жестких политиках управления сетевым трафиком (Egress Network Policies).
Маршрутизация вызова MCP-инструмента должна осуществляться через защитный прокси (Security Execution Proxy), который классифицирует задачу и направляет выполнение в наиболее изолированную среду с наименьшими привилегиями (Principle of Least Privilege).
[Агент генерирует Tool Call: execute_code / read_data]
│
▼
┌──────────────────────────────────┐
│ MCP Security Gateway Proxy │ ──(Schema & Capability Check)
└──────────────────────────────────┘
│
[Выбор рантайма по профилю изоляции]
┌───────────────────┴───────────────────┐
▼ ▼
┌───────────────────────────────┐ ┌───────────────────────────────┐
│ WASM / WASI Sandbox │ │ gVisor (runsc) MicroVM │
│ - Чистые вычисления / парсинг │ │ - Shell, Python, Node.js │
│ - Холодный старт: < 5 мс │ │ - User-space перехват syscalls│
│ - Нулевой доступ к FS и сети │ │ - Защита от Container Escape │
└───────────────────────────────┘ └───────────────────────────────┘
│ │
└───────────────────┬───────────────────┘
│
▼
┌──────────────────────────────────┐
│ Egress Network Filter (eBPF) │
│ - Блокировка 169.254.169.254 │ ──(Drop Unauthorized Packets)
│ - Блокировка RFC 1918 подсетей │
└──────────────────────────────────┘
│
▼
[Безопасный JSON-ответ агенту]169.254.169.254), предотвращающая кражу временных IAM-токенов в облаках AWS, GCP и Azure через SSRF-атаки.
runsc, ограничением RAM 256MB, CPU 0.5 ядра, read_only файловой системой и полным отключением сети (network_mode: none).http://10.0.0.0/8, http://192.168.0.0/16) и служебным адресам метаданных.Ниже представлена реализация архитектуры безопасного запуска MCP-инструментов. Включает декларативную конфигурацию контейнера gVisor и TypeScript-прокси для запуска вычислений в легковесной WASM-песочнице.
# Базовый образ для изолированного выполнения скриптов агента
FROM node:22-alpine
# Создание непривилегированного пользователя (Rootless)
RUN addgroup -S sandboxgroup && adduser -S sandboxuser -G sandboxgroup
# Подготовка временной директории с монтированием в tmpfs
WORKDIR /app
RUN chown -R sandboxuser:sandboxgroup /app
USER sandboxuser
# Входная точка исполнения изолированного кода
ENTRYPOINT ["node", "--disallow-code-generation-from-strings"]Конфигурация Docker Compose с рантаймом gVisor и изоляцией ресурсов:
services:
mcp-code-sandbox:
image: mcp-node-sandbox:latest
# Использование gVisor рантайма вместо стандартного runc
runtime: runsc
read_only: true
user: "10001:10001"
# Монтирование доступной для записи области только в оперативную память (tmpfs)
tmpfs:
- /tmp:rw,noexec,nosuid,size=64m
- /app/scratch:rw,noexec,nosuid,size=32m
# Полное отключение привилегий ядра Linux
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
# Строгие лимиты ресурсов для защиты от Fork Bomb и исчерпания памяти
pids_limit: 30
deploy:
resources:
limits:
cpus: '0.5'
memory: 256M
# Сетевая изоляция: контейнер не имеет внешнего доступа
network_mode: noneTypeScript-шлюз с поддержкой WebAssembly для мгновенного выполнения чистых алгоритмов и парсинга без накладных расходов на старт контейнеров:
import { z } from 'zod';
// Контракт входных параметров для MCP-инструмента
export const ExecuteWasmPayloadSchema = z.object({
wasmBinaryBase64: z.string(),
functionName: z.string().regex(/^[a-zA-Z0-9_]+$/),
inputNumber: z.number().int(),
timeoutMs: z.number().max(5000).default(2000),
});
export type ExecuteWasmPayload = z.infer<typeof ExecuteWasmPayloadSchema>;
export class McpWasmSandboxGateway {
/**
* Безопасное выполнение байт-кода WebAssembly в изолированном контексте V8
*/
public async executeInWasmSandbox(payload: ExecuteWasmPayload): Promise<{ result: number; executionTimeMs: number }> {
const { wasmBinaryBase64, functionName, inputNumber, timeoutMs } = ExecuteWasmPayloadSchema.parse(payload);
const wasmBuffer = Buffer.from(wasmBinaryBase64, 'base64');
// Ограничение размера WASM-бинарника (максимум 5 МБ)
if (wasmBuffer.length > 5 * 1024 * 1024) {
throw new Error('Payload limit exceeded: WASM binary size is capped at 5MB.');
}
const startTime = performance.now();
// Создание изолированного WebAssembly модуля
const wasmModule = await WebAssembly.compile(wasmBuffer);
// Окружение с нулевым импортом системных вызовов хоста (No Host System Imports)
const importObject = {
env: {
abort: () => {
throw new Error('WASM Execution Aborted by Runtime.');
},
},
};
const instance = await WebAssembly.instantiate(wasmModule, importObject);
const targetFn = instance.exports[functionName];
if (typeof targetFn !== 'function') {
throw new Error(`Target function '${functionName}' is not exported by WASM module.`);
}
// Выполнение с контролем тайм-аута (AbortController / Promise.race)
const executionPromise = Promise.resolve().then(() => (targetFn as (val: number) => number)(inputNumber));
const timeoutPromise = new Promise<never>((_, reject) =>
setTimeout(() => reject(new Error(`WASM execution timed out after ${timeoutMs}ms`)), timeoutMs)
);
const result = await Promise.race([executionPromise, timeoutPromise]);
const executionTimeMs = performance.now() - startTime;
return { result, executionTimeMs };
}
/**
* Валидация исходящего сетевого адреса для MCP-инструментов с поддержкой HTTP
*/
public static validateEgressUrl(rawUrl: string): URL {
const parsed = new URL(rawUrl);
// Запрет нестандартных протоколов
if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') {
throw new Error(`Forbidden protocol: ${parsed.protocol}`);
}
const hostname = parsed.hostname;
// Защита от обращения к Cloud Metadata Service (SSRF)
if (hostname === '169.254.169.254' || hostname === 'metadata.google.internal') {
throw new Error('Access Denied: Cloud Metadata Service access prohibited.');
}
// Блокировка приватных IP диапазонов (RFC 1918) и Localhost
const isPrivate = /^(localhost|127\.\d+\.\d+\.\d+|10\.\d+\.\d+\.\d+|192\.168\.\d+\.\d+|172\.(1[6-9]|2\d|3[01])\.\d+\.\d+)$/i.test(
hostname
);
if (isPrivate) {
throw new Error(`SSRF Block: Access to internal IP/host '${hostname}' is prohibited.`);
}
return parsed;
}
}Передача сокета Docker внутрь контейнера агента для «запуска вспомогательных сервисов». Наличие доступа к /var/run/docker.sock эквивалентно предоставлению неограниченных прав root на хосте: агент или злоумышленник через инъекцию может запустить привилегированный контейнер с монтированием корня / хост-машины.
Предоставление MCP-инструменту прав выполнять HTTP-запросы без жесткой фильтрации исходящих адресов (Egress). Атакующий через Prompt Injection формулирует команду запроса к http://169.254.169.254/latest/meta-data/iam/security-credentials/, эксфильтруя ключи AWS/GCP роли виртуальной машины.
Запуск произвольного сгенерированного кода в базовом контейнере Docker без специализированных рантаймов (gVisor, Firecracker). Контейнер разделяет ядро хоста. Известные CVE в Linux kernel (например, Dirty COW, переполнение eBPF) позволяют выполнить побег из контейнера на хост-сервер.
Если контейнер запускается без директивы pids_limit: 30 и жесткого timeout, сгенерированный агентом скрипт с бесконечной рекурсией (:(){ :|:& };:, Fork Bomb) исчерпает таблицу процессов операционной системы хоста, вызвав Kernel Panic (Denial of Service).
Zod-схема валидирует типы, отсекает внедрение shell-метасимволов и path-traversal последовательностей.
Инструмент маршрутизируется в WASM (для быстрых бессерверных вычислений) или gVisor (для выполнения системных скриптов).
Монтирование read-only файловой системы, сброс привилегий (CAP_DROP ALL), выделение 64MB tmpfs.
Сетевой экран блокирует трафик к служебным адресам 169.254.169.254, loopback и внутренним подсетям RFC 1918.
Завершение процесса по тайм-ауту, полное уничтожение контейнера и передача очищенного DTO обратно агенту.