Разрешения
Исходный код: src/permission.cc
Разрешения можно использовать для управления тем, к каким системным ресурсам имеет доступ процесс Node.js и какие действия он может выполнять с этими ресурсами. Разрешения также позволяют управлять тем, к каким модулям могут обращаться другие модули.
-
Разрешения на основе модулей определяют, какие файлы или URL доступны другим модулям во время выполнения приложения. Например, это можно использовать для управления доступом сторонних зависимостей к модулям.
-
Разрешения на уровне процесса определяют доступ процесса Node.js к ресурсам. Доступ к ресурсу можно полностью разрешить или запретить либо контролировать связанные с ним действия. Например, можно разрешить чтение файловой системы, запретив запись. Эта возможность не защищает от вредоносного кода. Согласно политике безопасности Node.js, Node.js доверяет любому коду, который ему поручено выполнить.
Модель разрешений реализует подход «ремня безопасности», предотвращая непреднамеренное изменение файлов или использование ресурсов доверенным кодом, которому явно не был предоставлен доступ. Она не обеспечивает гарантий безопасности при наличии вредоносного кода. Вредоносный код может обойти модель разрешений и выполнить произвольный код без ограничений, налагаемых этой моделью.
Если вы обнаружили потенциальную уязвимость безопасности, обратитесь к нашей политике безопасности.
Разрешения на основе модулей
Политики
Node.js включает экспериментальную поддержку создания политик при загрузке кода.
Политики — это функция безопасности, предназначенная для обеспечения целостности загружаемого кода.
Хотя эта функция не служит механизмом подтверждения происхождения кода, позволяющим отследить его источник, она обеспечивает надежную защиту от выполнения вредоносного кода. В отличие от моделей, работающих во время выполнения и ограничивающих возможности уже загруженного кода, политики Node.js направлены на то, чтобы вредоносный код вообще не был полностью загружен в приложение.
Использование политик предполагает безопасное обращение с файлами политик, например обеспечение невозможности их перезаписи приложением Node.js с помощью разрешений файловой системы.
Рекомендуется сделать манифест политики доступным только для чтения работающему приложению Node.js и обеспечить невозможность его изменения работающим приложением Node.js любым способом. В типичной конфигурации файл политики создается от имени пользователя, отличного от пользователя, запускающего Node.js, а пользователю, запускающему Node.js, предоставляются разрешения на чтение.
Включение
Флаг --experimental-policy можно использовать для включения функций политик при загрузке модулей.
После установки этого флага все модули должны соответствовать файлу манифеста политики, указанному в качестве его аргумента:
node --experimental-policy=policy.json app.js copy
Манифест политики будет использоваться для применения ограничений к коду, загружаемому Node.js.
Чтобы предотвратить подмену файлов политик на диске, можно указать целостность самого файла политики с помощью --policy-integrity. Это позволяет запускать node и проверять содержимое файла политики, даже если файл изменен на диске.
node --experimental-policy=policy.json --policy-integrity="sha384-SggXRQHwCG8g+DktYYzxkXRIkTiEYWBHqev0xnpCxYlqMBufKZHAHQM3/boDaI/0" app.js copy
Возможности
Поведение при ошибках
Если проверка политики завершается неудачей, Node.js по умолчанию выбрасывает ошибку. Поведение при ошибке можно изменить на один из нескольких вариантов, задав поле "onerror" в манифесте политики. Доступны следующие значения:
-
"exit": немедленно завершает процесс. Выполнение кода очистки не допускается. -
"log": записывает ошибку в журнал в месте сбоя. -
"throw": выбрасывает ошибку JS в месте сбоя. Это значение используется по умолчанию.
{
"onerror": "log",
"resources": {
"./app/checked.js": {
"integrity": "sha384-SggXRQHwCG8g+DktYYzxkXRIkTiEYWBHqev0xnpCxYlqMBufKZHAHQM3/boDaI/0"
}
}
} copy Проверки целостности
В файлах политик должны использоваться проверки целостности со строками Subresource Integrity, совместимыми с атрибутом integrity браузера и связанными с абсолютными URL.
При использовании require() или import все ресурсы, участвующие в загрузке, проверяются на целостность, если указан манифест политики. Если ресурс не соответствует значению целостности, указанному в манифесте, будет выброшена ошибка.
Пример файла политики, который разрешает загрузку файла checked.js:
{
"resources": {
"./app/checked.js": {
"integrity": "sha384-SggXRQHwCG8g+DktYYzxkXRIkTiEYWBHqev0xnpCxYlqMBufKZHAHQM3/boDaI/0"
}
}
} copy Для определения местоположения каждый ресурс, указанный в манифесте политики, может иметь один из следующих форматов:
- Строка относительного URL, указывающая на ресурс относительно манифеста, например
./resource.js,../resource.jsили/resource.js. - Полная строка URL ресурса, например
file:///resource.js.
При загрузке ресурсов URL должен совпадать целиком, включая параметры поиска и хеш-фрагмент. ./a.js?b не будет использоваться при попытке загрузить ./a.js, и наоборот.
Для создания строк целостности можно использовать такой скрипт, как node -e 'process.stdout.write("sha256-");process.stdin.pipe(crypto.createHash("sha256").setEncoding("base64")).pipe(process.stdout)' < FILE.
В качестве значения целостности можно указать логическое значение true, чтобы принимать любое содержимое ресурса; это может быть полезно при локальной разработке. В рабочей среде этого делать не рекомендуется, поскольку в таком случае неожиданное изменение ресурсов будет считаться допустимым.
Перенаправление зависимостей
Приложению может потребоваться включить исправленные версии модулей или запретить модулям предоставлять всем остальным модулям доступ ко всем модулям. Для этого можно перехватывать попытки загрузить модули, которые требуется заменить, и перенаправлять их.
{
"resources": {
"./app/checked.js": {
"dependencies": {
"fs": true,
"os": "./app/node_modules/alt-os",
"http": { "import": true }
}
}
}
} copy Ключами зависимостей служат строки запрошенных спецификаторов, а значениями могут быть true, null, строка с указанием модуля для разрешения или объект условий.
Строка спецификатора не выполняет поиск и должна точно совпадать с тем, что передано в require() или import, за исключением этапа канонизации. Поэтому, если для ссылки на один и тот же модуль используются разные строки (например, без расширения), в политике может потребоваться указать несколько спецификаторов.
Перед использованием для сопоставления строки спецификаторов канонизируются, но не разрешаются. Это обеспечивает некоторую совместимость с картами импорта. Например, если для ресурса file:///C:/app/utils.js в политике, расположенной по адресу file:///C:/app/policy.json, задано следующее перенаправление:
{
"resources": {
"file:///C:/app/utils.js": {
"dependencies": {
"./utils.js": "./utils-v2.js"
}
}
}
} copy Любой спецификатор, используемый для загрузки file:///C:/app/utils.js, будет перехвачен и перенаправлен на file:///C:/app/utils-v2.js независимо от того, является ли спецификатор абсолютным или относительным. Однако спецификатор, не являющийся строкой абсолютного или относительного URL, перехвачен не будет. Поэтому импорт вида import('#utils') не будет перехвачен.
Если значение перенаправления равно true, будет использоваться поле "dependencies" в верхнем уровне файла политики. Если это поле в верхнем уровне файла политики равно true, для поиска модуля используются стандартные алгоритмы поиска Node.js.
Если значением перенаправления является строка, она разрешается относительно манифеста и затем сразу используется без поиска.
Любая строка спецификатора, для которой выполняется попытка разрешения, но которая не указана в зависимостях, приводит к ошибке согласно политике.
Для карты зависимостей можно указать логическое значение true, чтобы разрешить модулю загружать любой спецификатор без перенаправления. Это может быть полезно при локальной разработке и иметь обоснованное применение в рабочей среде, однако использовать такую возможность следует осторожно и только после проверки модуля, подтверждающей корректность его поведения.
Как и "exports" в package.json, зависимости можно указать в виде объектов с условиями, определяющими способ загрузки зависимостей. В предыдущем примере "http" разрешен, если при загрузке присутствует условие "import".
Значение null для разрешенного значения приводит к сбою разрешения. Это можно использовать, чтобы явно запретить некоторые виды динамического доступа.
Неизвестные значения для местоположения разрешаемого модуля приводят к сбоям, но совместимость с будущими версиями не гарантируется.
Все гарантии перенаправления политик описаны в разделе Гарантии.
Пример: исправленная зависимость
Перенаправленные зависимости могут предоставлять урезанную или измененную функциональность в соответствии с требованиями приложения. Например, можно записывать данные о времени выполнения функций, обернув исходную функцию:
const original = require('fn');
module.exports = function fn(...args) {
console.time();
try {
return new.target ?
Reflect.construct(original, args) :
Reflect.apply(original, this, args);
} finally {
console.timeEnd();
}
}; copy Области действия
Используйте поле "scopes" манифеста, чтобы задать конфигурацию сразу для нескольких ресурсов. Поле "scopes" сопоставляет ресурсы по их сегментам. Если область или ресурс содержит "cascade": true, неизвестные спецификаторы будут искаться во включающей их области. Включающая область для каскадного поиска определяется путем рекурсивного сокращения URL ресурса: удаляются сегменты для специальных схем, сохраняются конечные суффиксы "/", удаляются запрос и хеш-фрагмент. В итоге URL сокращается до своего источника. Если URL не является специальным, область определяется по источнику URL. Если для источника область не найдена или источник является непрозрачным, в качестве области можно использовать строку протокола. Если область не найдена для протокола URL, будет использоваться последняя пустая строка "" в качестве области.
Обратите внимание: URL blob: наследуют источник от содержащегося в них пути, поэтому область "blob:https://nodejs.org" не будет иметь эффекта, так как ни один URL не может иметь источник blob:https://nodejs.org; URL, начинающиеся с blob:https://nodejs.org/, будут использовать https://nodejs.org в качестве источника и, следовательно, https: в качестве области протокола. Для URL blob: с непрозрачным источником областью протокола будет blob:, поскольку они не наследуют источник.
Пример
{
"scopes": {
"file:///C:/app/": {},
"file:": {},
"": {}
}
} copy Для файла, расположенного по адресу file:///C:/app/bin/main.js, области будут проверяться в следующем порядке:
"file:///C:/app/bin/"
Эта область определяет политику для всех ресурсов на основе файлов, расположенных в "file:///C:/app/bin/". Она отсутствует в поле "scopes" политики и поэтому будет пропущена. Если добавить эту область в политику, она будет использоваться перед областью "file:///C:/app/".
"file:///C:/app/"
Эта область определяет политику для всех ресурсов на основе файлов, расположенных в "file:///C:/app/". Она указана в поле "scopes" политики и будет определять политику для ресурса по адресу file:///C:/app/bin/main.js. Если для области задано "cascade": true, все запросы о ресурсе, которые не удалось удовлетворить, будут переданы следующей подходящей области для file:///C:/app/bin/main.js, "file:".
"file:///C:/"
Эта область определяет политику для всех ресурсов на основе файлов, расположенных в "file:///C:/". Она отсутствует в поле "scopes" политики и поэтому будет пропущена. Она не будет использоваться для file:///C:/app/bin/main.js, если только для "file:///C:/app/" не задано значение cascade или оно не указано в "scopes" политики.
"file:///"
Эта область определяет политику для всех ресурсов на основе файлов на localhost. Она отсутствует в поле "scopes" политики и поэтому будет пропущена. Она не будет использоваться для file:///C:/app/bin/main.js, если только для "file:///C:/" не задано значение cascade или оно не указано в "scopes" политики.
"file:"
Эта область определяет политику для всех ресурсов на основе файлов. Она не будет использоваться для file:///C:/app/bin/main.js, если только для "file:///" не задано значение cascade или оно не указано в "scopes" политики.
""
Эта область определяет политику для всех ресурсов. Она не будет использоваться для file:///C:/app/bin/main.js, если только для "file:" не задано значение cascade.
Целостность с использованием областей действия
Значение целостности true для области задает целостность true для любого ресурса, отсутствующего в манифесте.
Значение целостности null для области приводит к несовпадению проверки целостности любого ресурса, отсутствующего в манифесте.
Отсутствие значения целостности равносильно указанию null.
Значение "cascade" для проверок целостности игнорируется, если явно задано "integrity".
Следующий пример разрешает загрузку любого файла:
{
"scopes": {
"file:": {
"integrity": true
}
}
} copy Перенаправление зависимостей с использованием областей действия
Следующий пример разрешает доступ к fs для всех ресурсов в ./app/:
{
"resources": {
"./app/checked.js": {
"cascade": true,
"integrity": true
}
},
"scopes": {
"./app/": {
"dependencies": {
"fs": true
}
}
}
} copy Следующий пример разрешает доступ к fs для всех ресурсов data::
{
"resources": {
"data:text/javascript,import('node:fs');": {
"cascade": true,
"integrity": true
}
},
"scopes": {
"data:": {
"dependencies": {
"fs": true
}
}
}
} copy Пример: эмуляция карт импорта
Дана карта импорта:
{
"imports": {
"react": "./app/node_modules/react/index.js"
},
"scopes": {
"./ssr/": {
"react": "./app/node_modules/server-side-react/index.js"
}
}
} copy {
"dependencies": true,
"scopes": {
"": {
"cascade": true,
"dependencies": {
"react": "./app/node_modules/react/index.js"
}
},
"./ssr/": {
"cascade": true,
"dependencies": {
"react": "./app/node_modules/server-side-react/index.js"
}
}
}
} copy Карты импорта предполагают, что по умолчанию можно получить любой ресурс. Это означает, что для "dependencies" в верхнем уровне политики нужно задать значение true. В политиках требуется явно включать эту возможность, поскольку она связывает все ресурсы приложения, что не имеет смысла во многих сценариях. Кроме того, предполагается, что любая область имеет доступ ко всем областям выше по иерархии, помимо явно разрешенных зависимостей; во всех областях, эмулирующих карты импорта, необходимо задать "cascade": true.
Карты импорта имеют только одну область верхнего уровня для своих "imports". Поэтому для эмуляции "imports" используйте область "". Для эмуляции "scopes" используйте "scopes" аналогично тому, как в картах импорта работает "scopes".
Ограничения: политики не используют сопоставление строк для поиска областей различными способами. Вместо этого они обходят URL. Поэтому URL типа blob: и data: могут быть не полностью совместимы между этими двумя системами. Например, карты импорта могут частично сопоставлять URL data: или blob:, разделяя URL по символу /, тогда как политики намеренно не поддерживают такое поведение. Для URL blob: области карт импорта не наследуют источник URL blob:.
Кроме того, карты импорта работают только с import, поэтому может быть полезно добавить условие "import" ко всем сопоставлениям зависимостей.
Гарантии
- Политики гарантируют целостность файла при загрузке модуля с помощью
require(),import()илиnew Module(). - Перенаправление не препятствует доступу к API такими способами, как прямой доступ к
require.cache, который обеспечивает доступ к загруженным модулям. Перенаправление политик влияет только на спецификаторы дляrequire()иimport. - Модель угроз, связанная с подтверждением целостности модулей в политиках, предполагает, что после загрузки им разрешено вмешиваться в работу функций безопасности и даже обходить их, поэтому ожидается усиление защиты среды и времени выполнения.
Разрешения на уровне процесса
Модель разрешений
Модель разрешений Node.js — это механизм ограничения доступа к определенным ресурсам во время выполнения. API доступен при включении флага --experimental-permission; после включения он ограничивает доступ ко всем доступным разрешениям.
Доступные разрешения описаны в документации к флагу --experimental-permission.
При запуске Node.js с --experimental-permission будет ограничена возможность доступа к файловой системе через модуль fs, создания процессов, использования node:worker_threads, применения нативных аддонов и WASI, а также включения инспектора среды выполнения.
$ node --experimental-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'
} copy Разрешить создание процессов и потоков Worker можно с помощью флагов --allow-child-process и --allow-worker соответственно.
Чтобы разрешить использование нативных аддонов при включенной модели разрешений, используйте флаг --allow-addons. Для WASI используйте флаг --allow-wasi.
API среды выполнения
При включении модели разрешений с помощью флага --experimental-permission в объект process добавляется новое свойство permission. Это свойство содержит одну функцию:
permission.has(scope[, reference])
Вызов API для проверки разрешений во время выполнения (permission.has())
process.permission.has('fs.write'); // true
process.permission.has('fs.write', '/home/rafaelgss/protected-folder'); // true
process.permission.has('fs.read'); // true
process.permission.has('fs.read', '/home/rafaelgss/protected-folder'); // false copy Разрешения файловой системы
По умолчанию модель разрешений ограничивает доступ к файловой системе через модуль node:fs. Она не гарантирует, что пользователи не смогут получить доступ к файловой системе другими способами, например через модуль node:sqlite.
Чтобы разрешить доступ к файловой системе, используйте флаги --allow-fs-read и --allow-fs-write:
$ node --experimental-permission --allow-fs-read=* --allow-fs-write=* index.js Hello world! (node:19836) ExperimentalWarning: Permission is an experimental feature (Use `node --trace-warnings ...` to show where the warning was created) copy
Для обоих флагов допустимы следующие аргументы:
-
*— разрешает все операцииFileSystemReadилиFileSystemWriteсоответственно. - Пути, разделенные запятыми (
,), чтобы разрешить только соответствующие операцииFileSystemReadилиFileSystemWriteсоответственно.
Примеры:
-
--allow-fs-read=*— разрешает все операцииFileSystemRead. -
--allow-fs-write=*— разрешает все операцииFileSystemWrite. -
--allow-fs-write=/tmp/— разрешает доступFileSystemWriteк папке/tmp/. -
--allow-fs-read=/tmp/ --allow-fs-read=/home/.gitignore— разрешает доступFileSystemReadк папке/tmp/и пути/home/.gitignore.
Поддерживаются также подстановочные знаки:
-
--allow-fs-read=/home/test*разрешает доступ на чтение ко всему, что соответствует подстановочному знаку. Например:/home/test/file1или/home/test2
После указания подстановочного знака (*) все последующие символы игнорируются. Например, /home/*.js будет работать так же, как /home/*.
При инициализации модели разрешений к указанному каталогу автоматически добавляется подстановочный знак (*), если каталог существует. Например, если существует /home/test/files, он будет рассматриваться как /home/test/files/*. Однако если каталог не существует, подстановочный знак добавлен не будет, и доступ будет ограничен /home/test/files. Если нужно разрешить доступ к каталогу, которого пока нет, обязательно добавьте подстановочный знак явно: /my-path/folder-do-not-exist/*.
Использование модели разрешений с npx
Если вы используете npx для выполнения скрипта Node.js, модель разрешений можно включить, передав флаг --node-options. Например:
npx --node-options="--permission" package-name copy
Это устанавливает переменную окружения NODE_OPTIONS для всех процессов Node.js, запущенных командой npx, не затрагивая сам процесс npx.
Ошибка FileSystemRead при использовании npx
Приведенная выше команда, скорее всего, вызовет ошибку недопустимого доступа FileSystemRead, поскольку Node.js требуется доступ на чтение файловой системы для поиска и выполнения пакета. Чтобы избежать этого:
-
Использование глобально установленного пакета Предоставьте доступ на чтение к глобальному каталогу
node_modules, выполнив команду:npx --node-options="--permission --allow-fs-read=$(npm prefix -g)" package-name copy
-
Использование кэша
npxЕсли вы временно устанавливаете пакет или используете кэшnpx, предоставьте доступ на чтение к каталогу кэша npm:npx --node-options="--permission --allow-fs-read=$(npm config get cache)" package-name copy
Любые аргументы, которые обычно передаются в node (например, флаги --allow-*), также можно передать через флаг --node-options. Благодаря этой гибкости разрешения при использовании npx легко настроить нужным образом.
Ограничения модели разрешений
Перед использованием этой системы следует знать о следующих ограничениях:
- Модель не наследуется дочерним процессом Node.js или потоком Worker.
- При использовании модели разрешений ограничиваются следующие возможности:
- Нативные модули
- Дочерние процессы
- Потоки Worker
- Протокол инспектора
- Доступ к файловой системе
- WASI
- Модель разрешений инициализируется после настройки среды Node.js. Однако некоторые флаги, например
--env-fileили--openssl-config, предназначены для чтения файлов до инициализации среды. Поэтому на такие флаги правила модели разрешений не распространяются. То же относится к флагам V8, которые можно задать во время выполнения с помощьюv8.setFlagsFromString. - При включенной модели разрешений невозможно запрашивать движки OpenSSL во время выполнения, что влияет на встроенные модули crypto, https и tls.
- Использование существующих дескрипторов файлов через модуль
node:fsпозволяет обойти модель разрешений.
Ограничения и известные проблемы
- Символические ссылки будут отслеживаться даже в местоположениях за пределами набора путей, к которым предоставлен доступ. Относительные символические ссылки могут обеспечить доступ к произвольным файлам и каталогам. При запуске приложений с включенной моделью разрешений необходимо убедиться, что пути, к которым предоставлен доступ, не содержат относительных символических ссылок.
© Joyent, Inc. and other Node contributors
Licensed under the MIT License.
Node.js is a trademark of Joyent, Inc. and is used with its permission.
We are not endorsed by or affiliated with Joyent.
https://nodejs.org/dist/latest-v20.x/docs/api/permissions.html