Модель разрешений Node.js перешла из экспериментальной в стабильную в Node.js 26.3.
Узнайте, как использовать --permission, process.permission.has()
и необратимый process.permission.drop() для защиты ваших приложений.
Долгие годы обеспечение безопасности 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 следующие возможности
заблокированы по умолчанию:
node:fschild_process, cluster)worker_threads)require('node-addon-api'))| Область | Флаг | Проверка времени выполнения |
|---|---|---|
| Чтение ФС | --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') |
Простейший запуск блокирует всё:
$ 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.
При включении модели разрешений к объекту process добавляется новое
свойство permission. Оно предоставляет два метода для управления
разрешениями во время выполнения.
Проверяет, было ли предоставлено разрешение для указанной области и опционального ресурса:
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, проверьте разрешение заранее и предоставьте
понятную ошибку или альтернативный путь.
Это ключевая возможность — необратимый 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');
process.permission.drop('fs.read') отзывает весь доступ на чтение ФС.
*),
можно отозвать только всю область целиком. Нельзя отозвать отдельные ресурсы
внутри wildcard-предоставления.
--allow-fs-read=/my/folder), вы должны отозвать ту же самую
директорию, а не отдельные файлы внутри неё.
Реальная сила 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=/home/test* разрешает всё,
что соответствует шаблону (/home/test/file1, /home/test2 и т.д.).
# Разрешить все чтения файловой системы
--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 вы можете включить модель разрешений
с помощью флага --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
Запустите с --permission без флагов --allow-*. Запустите
приложение, отследите ошибки ERR_ACCESS_DENIED и предоставьте только
необходимые разрешения. Это гарантирует, что вы случайно не дадите слишком много.
Многие приложения требуют широких разрешений при запуске (чтение конфигов, открытие
соединений с БД), но почти не нуждаются в них после. Используйте
process.permission.drop() после инициализации, чтобы навсегда уменьшить
поверхность атаки:
// Инициализация приложения
initializeApp();
// Блокируем: отзываем запись в ФС, дочерние процессы и сеть
process.permission.drop('fs.write');
process.permission.drop('child');
// Теперь процесс может только читать файлы и отвечать на запросы
Перед потенциально ограниченной операцией проверьте разрешение:
if (process.permission.has('fs.write', '/tmp/cache')) {
fs.writeFileSync('/tmp/cache/data.json', JSON.stringify(data));
} else {
// Используем кеш в памяти
inMemoryCache.set(key, data);
}
--env-file и
--openssl-config читают файлы до настройки окружения и
не подчиняются модели разрешений.
node:sqlite может обращаться к файловой
системе независимо от ограничений node:fs.
Вот как выглядит типичное Express.js-приложение до и после внедрения модели разрешений:
$ node server.js
# Процесс имеет полный доступ:
# - К любому файлу на файловой системе
# - Ко всем сетевым интерфейсам
# - К запуску дочерних процессов
# - К любым нативным аддонам
# При компрометации атакующий получает контроль над всем сервером
$ 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-сервера с моделью разрешений:
// 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
Модель разрешений 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 26 с моделью разрешений? Провожу бесплатные консультации.