Модель разрешений Node.js: process.permission.drop() в Node 26.3
Безопасность · Июнь 2026

Модель разрешений Node.js:
process.permission.drop() в Node 26.3

Модель разрешений Node.js перешла из экспериментальной в стабильную в Node.js 26.3. Узнайте, как использовать --permission, process.permission.has() и необратимый process.permission.drop() для защиты ваших приложений.

Олег Максимов 5 июня 2026 14 мин чтения

Введение: ремень безопасности для Node.js-приложений

Долгие годы обеспечение безопасности Node.js-сервера означало использование OS-уровневых песочниц (seccomp, AppArmor, контейнеры) или сторонних модулей. У самого рантайма не было встроенного механизма сказать: «этот процесс может читать /var/www, но не /etc/passwd».

Всё изменилось с появлением модели разрешений Node.js — слоя безопасности времени выполнения, который контролирует доступ к файловой системе, сети, дочерним процессам, потокам Worker, нативным аддонам, WASI, FFI и инспектору V8. При включении через флаг --permission модель следует подходу «запрещено всё, что не разрешено явно» — вы предоставляете отдельные возможности по мере необходимости.

С выходом Node.js 26.3.0 (1 июня 2026) модель разрешений достигла Stability 2 — Стабильная. Флаг --experimental-permission был упразднён; --permission теперь — первоклассная, готовая к продакшену возможность.

Как работает модель разрешений

Модель разрешений использует подход «ремня безопасности»: она предотвращает случайный доступ доверенного кода к ресурсам, выходящим за рамки явно предоставленных. Она не защищает от вредоносного кода — Node.js доверяет любому коду, который ему поручено выполнить, и модель разрешений не может помешать решительному атакующему обойти её ограничения на уровне ОС.

При запуске Node.js с флагом --permission следующие возможности заблокированы по умолчанию:

Область Флаг Проверка времени выполнения
Чтение ФС --allow-fs-read process.permission.has('fs.read', путь)
Запись ФС --allow-fs-write process.permission.has('fs.write', путь)
Сеть --allow-net process.permission.has('net')
Дочерние процессы --allow-child-process process.permission.has('child')
Worker Threads --allow-worker process.permission.has('worker')
Нативные аддоны --allow-addons process.permission.has('addons')
WASI --allow-wasi process.permission.has('wasi')
FFI --allow-ffi process.permission.has('ffi')
Инспектор Отключается --permission process.permission.has('inspector')

Запуск с --permission

Простейший запуск блокирует всё:

$ node --permission index.js

Error: Access to this API has been restricted
    at node:internal/main/run_main_module:23:47 {
  code: 'ERR_ACCESS_DENIED',
  permission: 'FileSystemRead',
  resource: '/home/user/index.js'
}

Даже чтение входного файла запрещено! Модель правильно определяет, что не было предоставлено разрешений на чтение. Чтобы запустить приложение, укажите необходимые разрешения явно:

$ node --permission \
    --allow-fs-read=/home/user/project \
    --allow-fs-write=/tmp \
    app.js

Точка входа вашего приложения автоматически включается в список разрешённых файлов для чтения — app.js будет читаем даже без явного --allow-fs-read.

Runtime API: process.permission

При включении модели разрешений к объекту process добавляется новое свойство permission. Оно предоставляет два метода для управления разрешениями во время выполнения.

process.permission.has(scope, reference)

Проверяет, было ли предоставлено разрешение для указанной области и опционального ресурса:

process.permission.has('fs.write');                    // true (запись разрешена)
process.permission.has('fs.write', '/tmp/log.txt'); // true (разрешён /tmp)
process.permission.has('fs.read', '/etc/passwd');   // false (нет в разрешённых)
process.permission.has('child');                    // false (дочерние процессы запрещены)
process.permission.has('net');                      // false (сеть запрещена)

Это полезно для изящной деградации: вместо попытки выполнить операцию и перехвата ERR_ACCESS_DENIED, проверьте разрешение заранее и предоставьте понятную ошибку или альтернативный путь.

process.permission.drop(scope, reference)

Это ключевая возможность — необратимый API времени выполнения, который навсегда отзывает разрешение. После отзыва разрешение нельзя восстановить в том же процессе (метода permission.grant() не существует по замыслу).

const fs = require('node:fs');

// Читаем конфиг при запуске, пока есть доступ
const config = fs.readFileSync('/etc/myapp/config.json', 'utf8');

// Навсегда отзываем доступ на чтение к /etc/myapp после инициализации
process.permission.drop('fs.read', '/etc/myapp');

// Теперь это вызовет ERR_ACCESS_DENIED
process.permission.has('fs.read', '/etc/myapp/config.json'); // false

// Отзываем разрешение на запуск дочерних процессов целиком
process.permission.drop('child');

// Сетевая область — отзываем всё
process.permission.drop('net');

Важные правила permission.drop()

Принцип наименьших привилегий в действии

Реальная сила permission.drop() — возможность реализовать принцип наименьших привилегий во времени. Типичный паттерн:

// Фаза 1: Инициализация — нужен широкий доступ
const config = JSON.parse(fs.readFileSync('/etc/app/config.json', 'utf8'));
const db = require('better-sqlite3')('/var/lib/app/data.db');
process.permission.drop('fs.read', '/etc/app');
process.permission.drop('fs.write', '/var/lib/app');

// Фаза 2: Обработка запросов — нужны сеть + ограниченный доступ к ФС
const server = require('http').createServer((req, res) => {
  if (req.url === '/logs') {
    const log = fs.readFileSync('/var/log/app/request.log', 'utf8');
    res.end(log);
  }
});
server.listen(3000);

// После некоторого времени отзываем доступ и к логам
process.permission.drop('fs.read', '/var/log/app');

// Фаза 3: Осталась только сеть — процесс может отвечать,
// но не может читать/записывать файлы или запускать процессы

Разрешения файловой системы: подробно

Флаги --allow-fs-read и --allow-fs-write принимают несколько форм аргументов:

# Разрешить все чтения файловой системы
--allow-fs-read=*

# Разрешить чтение /tmp и /home/.gitignore
--allow-fs-read=/tmp/ --allow-fs-read=/home/.gitignore

# Разрешить запись в /tmp/
--allow-fs-write=/tmp/

# Разрешить чтение по wildcard
--allow-fs-read=/home/test*

При инициализации модели разрешений она автоматически добавляет wildcard (*), если указанная директория существует. Если директория не существует, wildcard не добавляется, и доступ ограничивается точным путём. Если вы хотите разрешить доступ к ещё не существующей папке, явно укажите wildcard: /my-path/folder-not-yet-exists/*.

Конфигурационные файлы

В дополнение к флагам командной строки разрешения можно объявить в конфигурационном файле Node.js с использованием флага --experimental-config-file. Создайте файл node.config.json:

{
  "permission": {
    "allow-fs-read": ["./foo", "./bar"],
    "allow-fs-write": ["./tmp"],
    "allow-child-process": true,
    "allow-worker": true,
    "allow-net": true,
    "allow-addons": false,
    "allow-ffi": false
  }
}
$ node --experimental-default-config-file app.js

Когда пространство имён permission присутствует в конфигурационном файле, Node.js автоматически включает флаг --permission — передавать его отдельно не нужно.

Использование модели разрешений с npx

При запуске скриптов через npx вы можете включить модель разрешений с помощью флага --node-options:

# Базовое включение
npx --node-options="--permission" package-name

# С доступом к ФС для глобальных модулей
npx --node-options="--permission --allow-fs-read=$(npm prefix -g)" package-name

# С доступом к кешу npx
npx --node-options="--permission --allow-fs-read=$(npm config get cache)" package-name

Лучшие практики безопасности

1. Начинайте с полного запрета, затем разрешайте выборочно

Запустите с --permission без флагов --allow-*. Запустите приложение, отследите ошибки ERR_ACCESS_DENIED и предоставьте только необходимые разрешения. Это гарантирует, что вы случайно не дадите слишком много.

2. Используйте drop() после инициализации

Многие приложения требуют широких разрешений при запуске (чтение конфигов, открытие соединений с БД), но почти не нуждаются в них после. Используйте process.permission.drop() после инициализации, чтобы навсегда уменьшить поверхность атаки:

// Инициализация приложения
initializeApp();

// Блокируем: отзываем запись в ФС, дочерние процессы и сеть
process.permission.drop('fs.write');
process.permission.drop('child');

// Теперь процесс может только читать файлы и отвечать на запросы

3. Изящная деградация с permission.has()

Перед потенциально ограниченной операцией проверьте разрешение:

if (process.permission.has('fs.write', '/tmp/cache')) {
  fs.writeFileSync('/tmp/cache/data.json', JSON.stringify(data));
} else {
  // Используем кеш в памяти
  inMemoryCache.set(key, data);
}

Ограничения и известные проблемы

До и после: миграция на модель разрешений

Вот как выглядит типичное Express.js-приложение до и после внедрения модели разрешений:

До: без модели разрешений

$ node server.js
# Процесс имеет полный доступ:
# - К любому файлу на файловой системе
# - Ко всем сетевым интерфейсам
# - К запуску дочерних процессов
# - К любым нативным аддонам
# При компрометации атакующий получает контроль над всем сервером

После: с моделью разрешений и runtime drop

$ node --permission \
    --allow-fs-read=/etc/app/config.json \
    --allow-fs-read=/var/www \
    --allow-fs-write=/var/log/app \
    --allow-net \
    server.js
const fs = require('node:fs');

// Фаза 1: Читаем конфиг и инициализируем
const config = JSON.parse(
  fs.readFileSync('/etc/app/config.json', 'utf8')
);

// Отзываем доступ к конфигу — он больше не нужен
process.permission.drop('fs.read', '/etc/app/config.json');

// Фаза 2: Остались только /var/www и /var/log/app
const express = require('express');
const app = express();

app.use(express.static('/var/www'));
app.post('/api/data', (req, res) => {
  fs.writeFileSync('/var/log/app/requests.log', data);
  res.json({ ok: true });
});

app.listen(3000);

Ключевое отличие: после первого вызова process.permission.drop(), даже если приложение скомпрометировано, атакующий не сможет прочитать /etc/app/config.json — разрешение было безвозвратно отозвано.

Практический пример: безопасный API-сервер

Собираем всё вместе в полном примере защищённого API-сервера с моделью разрешений:

// secure-server.js
const fs = require('node:fs');
const http = require('node:http');

// === ФАЗА ИНИЦИАЛИЗАЦИИ ===

// Загружаем секреты из ограниченных путей
const dbConfig = JSON.parse(
  fs.readFileSync('/secrets/database.json', 'utf8')
);
const apiKey = fs.readFileSync('/secrets/api-key.txt', 'utf8').trim();

// Отзываем ВЕСЬ доступ к чтению ФС — секреты загружены
process.permission.drop('fs.read');

// Отзываем ВЕСЬ доступ к записи ФС — файлы не должны меняться
process.permission.drop('fs.write');

// Отзываем дочерние процессы — серверу они не нужны
process.permission.drop('child');

// Отзываем Worker — не нужны для этого сервиса
process.permission.drop('worker');

// === ФАЗА ОБРАБОТКИ ЗАПРОСОВ ===
// Осталось только разрешение на сеть

const server = http.createServer((req, res) => {
  if (req.url === '/health') {
    res.writeHead(200, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ status: 'ok', permissions: 'locked' }));
    return;
  }

  res.writeHead(200);
  res.end('Hello from locked-down process');
});

server.listen(3000, () => {
  console.log('Server running on port 3000');
});

Этот паттерн — инициализация, отзыв разрешений, обработка запросов — практическое воплощение принципа наименьших привилегий для серверных Node.js-приложений.

Для общего обзора всех новых возможностей Node.js 26 читайте мой полный гайд по Node.js 26 — Temporal API, V8 14.6 и Undici 8.

Нужна безопасная разработка? Мои услуги по Node.js

FAQ

Что такое модель разрешений Node.js?
Модель разрешений Node.js — это механизм безопасности, ограничивающий доступ процесса Node.js к системным ресурсам: файловой системе, сети, дочерним процессам, потокам Worker, нативным аддонам, WASI, FFI и инспектору V8. При включении через флаг --permission все разрешения по умолчанию запрещены, и вы явно предоставляете доступ через флаги --allow-*. Это подход «ремня безопасности»: он предотвращает случайный выход доверенного кода за рамки явно предоставленных разрешений.
Как работает process.permission.drop()?
process.permission.drop(scope, reference) — это необратимый API времени выполнения, который отзывает разрешения. Без аргумента отзывается вся область целиком. С аргументом отзывается разрешение только для конкретного ресурса. Можно отозвать только тот ресурс, который был явно предоставлен. Wildcard-разрешения требуют отзыва всей области. Отзыв не закрывает уже открытые ресурсы (файловые дескрипторы, сетевые соединения).
Изменилась ли стабильность модели разрешений в Node.js 26.3?
Да. Node.js 26.3.0 (1 июня 2026) убрал префикс --experimental из флага --permission. Модель разрешений перешла из статуса «Экспериментальная» в «Стабильная» (Stability 2). API считается готовым к продакшену с гарантиями обратной совместимости на весь срок жизни Node.js 26. Основные API process.permission.has() и process.permission.drop() не изменились.
Какие области контролирует модель разрешений?
Модель контролирует: FileSystemRead (--allow-fs-read), FileSystemWrite (--allow-fs-write), ChildProcess (--allow-child-process), WorkerThreads (--allow-worker), NativeAddons (--allow-addons), WASI (--allow-wasi), FFI (--allow-ffi), Network (--allow-net) и Inspector (отключается --permission). Каждая область проверяется через process.permission.has() и отзывается через process.permission.drop().
Можно ли использовать модель разрешений с npx?
Да. Используйте флаг --node-options: npx --node-options='--permission' package-name. Вероятно, потребуется доступ к файловой системе: npx --node-options='--permission --allow-fs-read=$(npm prefix -g)' package-name. Все флаги --allow-* работают внутри --node-options, который устанавливает NODE_OPTIONS для всех процессов Node.js, запущенных npx.
Каковы ограничения модели разрешений Node.js?
Основные ограничения: (1) Модель не наследуется Worker. (2) Флаги --env-file и --openssl-config читают файлы до инициализации и обходят модель. (3) Доступ через открытые файловые дескрипторы обходит модель. (4) Символические ссылки ведутся за пределы разрешённых путей. (5) process._debugProcess() может сигнализировать другим процессам Node.js. (6) node:sqlite может обходить ограничения ФС.
Как проверить разрешения во время выполнения?
Используйте process.permission.has(scope, reference) для проверки активного разрешения. Например: process.permission.has('fs.write', '/tmp') возвращает true, если доступ к записи в /tmp разрешён. process.permission.has('child') возвращает true, если разрешён запуск дочерних процессов. Это полезно для изящной деградации — проверяйте перед операциями, которые могут выбросить ERR_ACCESS_DENIED.

Готовы защитить ваше Node.js-приложение?

Модель разрешений Node.js, теперь стабильная в Node.js 26.3, привносит безопасность времени выполнения в JavaScript-рантайм практичным и легко внедряемым способом. API process.permission.drop() — возможность необратимо отзывать разрешения после инициализации — особенно эффективен для реализации принципа наименьших привилегий в долгоживущих серверных процессах.

Если вы создаёте Node.js-приложения и хотите добавить proper security hardening, я могу помочь вам разработать стратегию разрешений для вашей архитектуры. Свяжитесь со мной для бесплатной консультации по безопасности вашего проекта.

Я — full-stack разработчик с 20-летним опытом создания и защиты Node.js-приложений — от стартапов до enterprise-платформ. Нахожусь в Минске и работаю по всему миру, давайте обсудим ваш проект.

Связаться

Защитите ваш Node.js-проект

Нужна помощь в усилении безопасности Node.js-приложения или миграции на Node.js 26 с моделью разрешений? Провожу бесплатные консультации.