← Блог
Engineering

Как безопаснее всего выполнять недоверенный пользовательский код Node.js или Python на моем бэкенде?

Yura Dubinin
Yura Dubinin
Резюмировать с помощью:

Запускайте недоверенный пользовательский код в одноразовой среде выполнения вне вашего веб-бэкенда. Для публичного сервиса, который принимает произвольный код Node.js или Python, надежный базовый вариант — правильно настроенная микро-ВМ для каждой задачи или сеанса отдельного клиента. Не передавайте в нее учетные данные продакшена, блокируйте ненужный доступ к сети и задавайте лимиты ресурсов и срок действия извне среды выполнения.

Используйте приложение на Express, FastAPI или Django для аутентификации пользователей, постановки задач в очередь и возврата результатов. Поручите управление выполнением отдельному воркеру. Одних ограничений на уровне языка недостаточно для изоляции, которая нужна этой архитектуре.

Выбор конфигурации зависит от того, что могут отправлять пользователи. Калькулятору достаточно небольшого языка выражений. Агенту для работы с кодом, который устанавливает пакеты и выполняет команды оболочки, нужна полноценная среда выполнения. Мы рассмотрим второй случай, включая код, который ИИ создает на основе пользовательского ввода.

Сначала выберите границу изоляции

ПодходЧто он разделяетПригодность для произвольного пользовательского кода
node:vm, Python eval или execКонтексты языка или области выполненияНедостаточно в качестве границы безопасности
Дочерний процессПамять и жизненный цикл процессовПо-прежнему нужны ограничения ОС, файловой системы, сети и ресурсов
Обычный DockerПространства имен, представления файловой системы и группы ресурсовПолезные средства контроля, но контейнеры используют общее ядро хоста Docker
gVisorЯдро приложения обрабатывает системные вызовы до того, как они достигнут хостаПодходит, если совместимость и производительность отвечают требованиям задачи
Микро-ВМГостевое ядро и граница виртуальной машиныНадежный базовый вариант для вредоносного кода; хост и гостевую систему все равно нужно обновлять

Безопасность Docker зависит от хоста, демона и конфигурации. gVisor добавляет ядро приложения в пространстве пользователя. Firecracker использует KVM для запуска микро-ВМ. Ни один из этих вариантов не определяет, какие учетные данные или сетевые адреса должно предоставлять ваше приложение. Безопасность Docker, архитектура gVisor, Firecracker.

При более строгих требованиях рассмотрите также выделенные хосты или аккаунты для воркеров. Граница ВМ снижает риски общего ядра, но не гарантирует, что любой выход из изоляции или аппаратный побочный канал невозможен.

Почему Node.js и Python нужна внешняя песочница

Node.js: контекст VM не служит границей безопасности

В документации Node прямо сказано, что node:vm нельзя использовать как механизм защиты от недоверенного кода. Передача пустого объекта или тайм-аута в vm.runInNewContext этого не меняет. Документация Node.js VM.

Рабочие потоки помогают выполнять задачи параллельно. Дочерний процесс может защитить веб-сервер от обычного сбоя интерпретатора. Ни то ни другое не закрывает автоматически доступ к файловой системе, унаследованным переменным окружения или сети. Дочерний процесс с правами вашего сервера в ОС по-прежнему имеет доступ к ресурсам, которые разрешают эти права.

В описании Permission Model в Node также сказано, что она не гарантирует защиту от вредоносного кода. Используйте ее как дополнительное ограничение там, где это полезно, а для вредоносного пользовательского кода задавайте внешнюю границу изоляции. Разрешения Node.js.

Python: ограничение имен не изолирует процесс

Python предупреждает, что exec и eval выполняют произвольный код. Удаление __builtins__ или блокировка нескольких импортов не превращают Python без ограничений в безопасный сервис выполнения кода. RestrictedPython также описывает подмножество языка и не утверждает, что сам по себе предоставляет песочницу. Встроенные функции Python, RestrictedPython.

Для данных принимайте JSON-документ ограниченного размера и проверяйте его структуру. ast.literal_eval не выполняет произвольный код Python, но документация Python предупреждает, что вредоносный ввод может исчерпать память, ресурсы CPU и стек. Кроме того, эта функция не поддерживает произвольные программы. Документация Python AST.

Для полноценного Python запускайте отдельный интерпретатор внутри выбранной песочницы. Разбор веб-запросов и аутентификацию пользователей выполняйте за ее пределами.

Архитектура бэкенда для выполнения недоверенного кода

Запуск недоверенного кода через API с аутентификацией, очередь и процесс контроля выполнения в отдельной одноразовой песочнице

Бэкенд должен управлять записью о задаче и правилами доступа:

  1. Аутентифицируйте отправителя и проверьте его квоту. Ограничьте размер запроса в байтах до буферизации исходного кода.
  2. Сохраните исходный код как данные. Привяжите задачу к клиенту и выберите среду выполнения из фиксированного списка разрешенных вариантов.
  3. Поставьте задачу в очередь. Доверенный воркер создает новую песочницу с явно заданными лимитами и сроком действия.
  4. Передайте только входные данные задачи. Выполните фиксированную команду интерпретатора, передав исходный код через файл или stdin.
  5. Соберите вывод в пределах лимита и статус. Проверьте результаты перед возвратом или использованием в другом сервисе.
  6. Остановите и удалите среду выполнения. Отдельный воркер очистки обрабатывает брошенные задачи.

Храните ключ провайдера песочниц, сокет Docker и учетные данные для управления облаком в доверенном воркере. Пользовательский код не должен получать учетные данные, которые позволяют создавать другие песочницы или управлять ими.

Проверяйте принадлежность клиенту при каждой операции: выполнении, отмене, скачивании, возобновлении и удалении. Идентификатор песочницы, который передал клиент, — это идентификатор, а не доказательство права доступа. Более общий процесс выполнения описан в нашем руководстве по песочницам для ИИ.

Какие ограничения задать до первой пользовательской задачи

ОграничениеНачальная политика для небольшого сервиса выполнения кода
CPU и RAMФиксированный объем ресурсов; отклоняйте переданные пользователем флаги ресурсов
Полное время выполненияКороткий срок выполнения, заданный извне, и максимальное время жизни песочницы
Процессы и файлыЛимиты числа процессов, открытых файлов и доступного для записи хранилища
Ввод и выводНапример, 32 KiB исходного кода и 64 KiB суммарного stdout/stderr; выберите лимиты для своего продукта
Файловая системаБазовый образ только для чтения и небольшой временный каталог; не монтируйте домашний каталог или приложение с хоста
Учетные данныеНикаких секретов продакшена; только при необходимости предоставляйте краткосрочный доступ с узкими правами
СетьПо умолчанию запрещайте исходящий доступ; явно разрешайте нужные адреса
Состояние клиентаНовое рабочее пространство для каждой задачи или сеанс, привязанный к одному клиенту
Использование аккаунтаЛимиты параллельных задач, частоты отправки и расходов для каждого пользователя

Выделенные ресурсы CPU ограничивают скорость выполнения бесконечного цикла. Ограничение по реальному времени определяет, как долго он сможет работать. Применяйте оба ограничения. Документация Docker указывает, что контейнеры не имеют ограничений CPU или памяти, пока вы их не настроите. Ограничения ресурсов Docker.

Сетевая политика должна охватывать внутренние сервисы и метаданные облака наряду с публичным интернетом. Если задачам нужен HTTP, применяйте список разрешенных адресов на исходящем прокси-сервере или сетевом уровне, который гостевая система не может изменить. Проверяйте разрешение DNS, адреса назначения IPv4/IPv6 и перенаправления. Обертка над fetch внутри гостевой системы не может контролировать каждую программу, которую эта система способна запустить. Предотвращение SSRF по OWASP.

Проверенные примеры запуска Docker для Node.js и Python

Эти команды показывают ограничения при запуске контейнера на фиксированных примерах программ. Они не включают внешний процесс контроля времени выполнения и объема вывода, необходимый для кода от пользователей публичного сервиса. Запускайте их на воркере выполнения, отдельном от серверов приложения. Для публичного сервиса выполнения вредоносного кода поместите этот воркер в выбранную ВМ или используйте подходящую среду выполнения песочницы.

Используйте доверенный образ с установленными обновлениями безопасности. Теги ниже удобны для примера; при развертывании фиксируйте проверенный дайджест образа и обновляйте его в рамках обычного процесса установки исправлений. Заранее скачивайте образы, прежде чем принимать задачи.

Пример для Node.js

docker run --rm -i --init \
  --network none --read-only \
  --tmpfs /tmp:rw,noexec,nosuid,nodev,size=16m \
  --user 65534:65534 --cap-drop ALL \
  --security-opt no-new-privileges \
  --memory 128m --memory-swap 128m --cpus 0.5 \
  --pids-limit 32 --ulimit nofile=64:64 \
  --log-driver none \
  node:24-bookworm-slim node - <<'JS'
console.log(JSON.stringify({answer: 6 * 7}));
JS

Ожидаемый вывод — {"answer":42}. Команда работает с UID 65534, сбрасывает Linux capabilities, оставляет корневую файловую систему доступной только для чтения и разрешает запись в небольшой /tmp. Она не предоставляет внешний сетевой интерфейс и не монтирует каталоги хоста.

Пример для Python

docker run --rm -i --init \
  --network none --read-only \
  --tmpfs /tmp:rw,noexec,nosuid,nodev,size=16m \
  --user 65534:65534 --cap-drop ALL \
  --security-opt no-new-privileges \
  --memory 128m --memory-swap 128m --cpus 0.5 \
  --pids-limit 32 --ulimit nofile=64:64 \
  --log-driver none \
  python:3.13-slim-bookworm python -I -B -u - <<'PYCODE'
import json
print(json.dumps({"answer": 6 * 7}))
PYCODE

Ожидаемый вывод — {"answer": 42}. Параметр Python -I уменьшает влияние путей импорта и окружения интерпретатора. Он не мешает программе использовать ресурсы ОС, которые разрешает ее контейнер. Параметры командной строки Python.

Сохраните стандартный профиль seccomp в Docker. no-new-privileges блокирует повышение привилегий через такие механизмы, как исполняемые файлы с setuid. Режим --network none оставляет только петлевой интерфейс. У этих ограничений разные задачи; ни одно не заменяет остальные. Параметры Docker run, seccomp, сетевая изоляция.

Параметр noexec временного каталога запрещает прямой запуск файлов с этой точки монтирования, но интерпретатор по-прежнему может прочитать и выполнить хранящийся там скрипт. Он не запрещает пользовательские программы. Лимит 128 MiB также подходит для этих небольших примеров, но не для каждого пакета npm или задачи анализа данных.

Подключение к бэкенду без подстановки в команды оболочки

На доверенном воркере формируйте запрос к Docker или провайдеру из фиксированных аргументов. В Node можно использовать spawn с shell: false; в Python — subprocess.Popen со списком аргументов. Передавайте присланный исходный код через stdin или файл по фиксированному пути. Никогда не помещайте его внутрь команды оболочки, например в строку, переданную в sh -c. Смотрите дочерние процессы Node и безопасность subprocess в Python.

Разрешайте пользователям выбирать среду выполнения по имени из утвержденного списка. Не позволяйте передавать в запросе флаги Docker, произвольные образы, пути хоста или имена переменных окружения. Установка пакетов тоже выполняет код; если вы ее поддерживаете, применяйте к ней те же правила изоляции и ограничения ресурсов.

Останавливайте задачу, ограничивайте вывод и подтверждайте очистку

Тайм-аут HTTP-запроса или вызова SDK может прекратить ожидание на бэкенде, пока удаленная программа продолжает работать. Аналогично, завершение локального процесса Docker CLI может оставить его контейнер работающим.

Доверенный процесс контроля выполнения должен:

  • Записать идентификатор контейнера или песочницы до начала выполнения.
  • Читать stdout и stderr по мере поступления с общим лимитом байтов.
  • При истечении времени или превышении объема вывода останавливать саму задачу через API среды выполнения.
  • Закрывать подключенные потоки или вычитывать их, не сохраняя неограниченный объем данных.
  • Подтверждать завершение, удалять временное состояние и записывать все ошибки очистки.

Параметр --log-driver none в примерах не дает Docker сохранять логи контейнера. Ваш бэкенд все равно должен ограничивать объем читаемых потоков. В Docker останавливайте контейнер по имени до завершения подключенного CLI. Для сервиса микро-ВМ используйте операцию завершения из его документации и проверяйте конечное состояние.

Задайте срок действия на стороне провайдера, если он поддерживает эту возможность. Блок finally не выполнится после отключения питания воркера, поэтому запускайте по расписанию процесс очистки, который сверяет записи о просроченных задачах с работающими ресурсами. Сделайте повторную очистку безопасной. Не отмечайте задачу без управляющего процесса как успешно очищенную только потому, что клиент отключился.

Что мы проверили

31 октября 2026 года мы выполнили обе команды в Docker Desktop с Engine 28.1.1, ядром Linux 6.10.14-linuxkit, ARM64, Node.js 24.22.2 и Python 3.13.18.

ПроверкаРезультат для обеих сред выполнения
Фиксированное вычислениеВозвращено 42
Эффективный пользовательUID 65534
Запись в /etcЗаблокирована
Запись и чтение в /tmpРаботали
Исходящий сокет к тестовому адресуОшибка ENETUNREACH
Тестовая метка в окружении только на хостеОтсутствовала внутри контейнера
Конфигурация ресурсов128 MiB памяти, без swap, 32 процесса/потока, половина CPU
Политика привилегийNoNewPrivs включен
Бесконечный цикл под контролем тестового процессаОстановлен по истечении двухсекундного срока
Избыточный вывод под контролем тестового процессаСбор остановлен на 64 KiB; выполнение задачи прекращено
Новый контейнер после предыдущей задачиПредыдущий временный файл отсутствовал

Наша первая проверка ограничения вывода выявила ошибку очистки: подключенный Docker CLI ожидал освобождения заполненного канала после остановки контейнера. Мы исправили тестовый процесс контроля, чтобы он сначала останавливал задачу, а затем завершал подключенный клиент. Поэтому одной команды запуска недостаточно для полноценного сервиса выполнения кода.

Эти проверки охватывают локальную конфигурацию контейнера и поведение процесса контроля. Они не проверяют выход из изоляции через ядро, не доказывают блокировку всех путей доступа и не тестируют изоляцию микро-ВМ Lizard. Повторите проверки в той среде выполнения воркера, которую вы развертываете.

Считайте результаты и сохраненное состояние недоверенными

Песочница может изолировать программу, но ее вывод при этом способен атаковать следующую систему. Экранируйте stdout перед отображением. Проверяйте возвращаемый JSON. Никогда не десериализуйте присланный Python pickle и не выполняйте на бэкенде возвращенную строку JavaScript. Предупреждение о Python pickle, обработка вывода по OWASP.

Отдавайте созданный HTML и страницы предпросмотра с источника (origin), который не использует общие учетные данные с вашей панелью управления. Для скачиваемых файлов контролируйте имена, размеры и типы; не переходите по созданным гостевой системой символическим ссылкам к путям хоста. Также ограничивайте объем работы при извлечении архивов и распаковке данных.

Чистое начальное состояние не дает одному клиенту читать файлы, кеши пакетов или значения в памяти другого клиента. Постоянным сеансам нужны явные правила принадлежности и хранения. Приостановленная песочница сохраняет состояние; она не должна становиться чистой средой следующего пользователя. Применяйте те же правила к снимкам состояния и общим томам.

Как использовать Lizard

Lizard Sandboxes выполняет код в микро-ВМ Firecracker, каждая со своим ядром. Lizard SDK поддерживает JavaScript/TypeScript и Python, включая операции с файлами, выполнение команд, проверки статуса и управление жизненным циклом. Ваш бэкенд может управлять средой выполнения через этот API вместо обслуживания собственных хостов воркеров. Документация Lizard Sandboxes.

Храните ключ API управления на бэкенде. Для задач с вредоносным кодом не передавайте lizardToken, который делает учетные данные проекта доступными внутри гостевой системы, и не подключайте учетные данные агентов, которые задаче не нужны. Явно задавайте время жизни и завершайте одноразовые задачи после сбора результатов в пределах лимита. Справочник Lizard SDK.

Изоляция Firecracker не означает блокировку исходящего трафика. Проверьте, что действующая сетевая политика ограничивает доступ требуемыми адресами. Текущий справочник параметров создания в SDK не описывает список разрешенных исходящих адресов; не придумывайте флаг и не считайте, что настройка прокси внутри гостевой системы обеспечивает такое ограничение. Если строгий контроль исходящего трафика обязателен, проверьте конфигурацию, которая способна его обеспечить, прежде чем принимать такую задачу.

Сначала определите разрешенные для задачи файлы, доступ к сети и бюджет ресурсов. Затем протестируйте новую среду с этими ограничениями. Используйте краткое руководство Lizard для работы с API выполнения и наше сравнение цен на песочницы для оценки затрат.

Разрабатывайте с ИИ. Развёртывайте с Lizard.

Вам не нужна платформенная команда, чтобы выйти в продакшн. Весь ваш облак — в одной команде CLI.

Начать бесплатно
Сервисы
—
Аддоны
—
Развёртывания
—
Запуски sandbox
—

Мы используем cookie для основной работы сайта и аналитики. См. нашу политику cookie.