Безопасность и разрешения
Deno безопасен по умолчанию. Если вы не разрешите это явно, программа, запущенная с помощью Deno, не получит доступа к чувствительным API, таким как доступ к файловой системе, сетевое подключение или доступ к окружению. Вы должны явно предоставить доступ к этим ресурсам с помощью флагов командной строки или запроса разрешения во время выполнения. Это существенное отличие от Node, где зависимости автоматически получают полный доступ ко всем операциям ввода-вывода системы, что потенциально может приводить к появлению скрытых уязвимостей в вашем проекте.
Прежде чем использовать Deno для запуска полностью недоверенного кода, прочитайте приведённый ниже раздел о выполнении недоверенного кода.
Ключевые принципы
Прежде чем перейти к подробностям разрешений, важно понять ключевые принципы модели безопасности Deno:
- По умолчанию доступ к вводу-выводу отсутствует: код, выполняющийся в среде выполнения Deno, не имеет доступа к чтению или записи произвольных файлов в файловой системе, отправке сетевых запросов или открытию сетевых прослушивающих сокетов, переменным окружения или созданию дочерних процессов.
-
Нет ограничений на выполнение кода с тем же уровнем привилегий: Deno позволяет выполнять любой код (JS/TS/Wasm) разными способами, в том числе с помощью
eval,new Function, динамического импорта и веб-воркеров на том же уровне привилегий, практически не ограничивая происхождение кода (сеть, npm, JSR и т. д.). - Несколько запусков одного и того же приложения могут обмениваться данными: Deno предоставляет механизм обмена данными между несколькими запусками одного приложения посредством встроенного кэширования и API хранилища KV. Разные приложения не могут видеть данные друг друга.
- Весь код, выполняющийся в одном потоке, имеет один и тот же уровень привилегий: весь код, выполняющийся в одном потоке, имеет один и тот же уровень привилегий. Разные модули не могут иметь разные уровни привилегий в одном потоке.
- Код не может повысить свои привилегии без согласия пользователя: код, выполняющийся в среде выполнения Deno, не может повысить свои привилегии без явного согласия пользователя на повышение — через интерактивный запрос или флаг при запуске.
-
Начальный статический граф модулей может импортировать модули без ограничений: все модули, импортированные в начальном статическом графе модулей (локальные файлы, пакеты npm, пакеты jsr и удалённые URL-адреса), загружаются средой выполнения без обращения к системе разрешений. Для загрузки локальных файлов не требуется
--allow-read, а для получения удалённых модулей не требуется--allow-net. Статический граф включает статические инструкцииimportи вызовыimport(), спецификатор которых представляет собой строковый литерал: всё, что можно разрешить, не выполняя код. Это исключение относится только к загрузке. После запуска кода все его действия по-прежнему контролируются системой разрешений, а вызовыimport()со спецификаторами, не являющимися литералами (например,import(someVariable)), проверяются во время выполнения на соответствие--allow-read/--allow-import.
Эти ключевые принципы призваны создать среду, в которой пользователь может выполнять код с минимальным риском причинить вред хост-компьютеру или сети. Модель безопасности разработана так, чтобы её было легко понять и чтобы она обеспечивала чёткое разделение ответственности между средой выполнения и выполняемым в ней кодом. Модель безопасности обеспечивается средой выполнения Deno и не зависит от базовой операционной системы.
Разрешения
По умолчанию Deno работает в песочнице: код не может обращаться к файловой системе, сети, окружению или запускать дочерние процессы, пока вы не разрешите это. Разрешения не выдаются по принципу «всё или ничего». Вы предоставляете их с помощью флагов --allow-*, и большинство разрешений можно ограничить определёнными ресурсами, чтобы программа получала только необходимый ей доступ и ничего сверх того.
Если запустить программу без флагов, операция, требующая чувствительного доступа, будет отклонена:
deno run main.ts
# error: Requires net access to "example.com", run again with the --allow-net flag
Предоставьте доступ только к хосту, к которому программе нужно подключиться:
deno run --allow-net=example.com main.ts
Такое же ограничение области действия применяется к другим разрешениям: --allow-read=./data ограничивает чтение одним каталогом, --allow-env=API_KEY — одной переменной и так далее. Флаг --allow-net без значения предоставляет доступ ко всей категории, что шире, чем нужно большинству программ.
Если операция отклонена (поскольку разрешение не было предоставлено или было явно запрещено), Deno выбрасывает ошибку NotCapable. Её можно перехватить, как и любую другую ошибку, чтобы корректно обработать отсутствие разрешений:
try {
await Deno.readTextFile("/etc/hosts");
} catch (err) {
if (err instanceof Deno.errors.NotCapable) {
console.error("Missing read permission, run again with --allow-read");
}
}
Если stdout подключён к терминалу и вы не передали флаг, Deno приостанавливает выполнение и запрашивает разрешение, а не завершается с ошибкой. Таким образом, вы можете предоставлять доступ интерактивно по мере необходимости:
⚠️ Deno requests net access to "example.com". Run again with --allow-net to bypass this prompt.
Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all net permissions) >
Флаги --deny-* имеют приоритет над соответствующими флагами --allow-*, поэтому можно предоставить доступ ко всей категории, исключив из неё чувствительные области: --allow-read --deny-read=/etc разрешает чтение всего, кроме /etc. На другом конце спектра — -A (--allow-all): он предоставляет полный доступ и полностью отключает песочницу, обеспечивая тот же уровень доступа, что и при запуске кода в Node, поэтому используйте его с осторожностью.
См. справочник по разрешениям, где перечислены все разрешения и соответствующие флаги, а также раздел permissions в deno.json, посвящённый их объявлению в файле конфигурации.
Разрешения, обходящие песочницу
Некоторые разрешения предоставляют доступ к тому, что Deno не может изолировать в песочнице, поэтому их выдача фактически означает предоставление полного доступа:
-
--allow-runпозволяет программе создавать дочерние процессы. Дочерний процесс запускается как отдельная программа со своими разрешениями, а не с ограниченным набором разрешений, предоставленным процессу Deno, поэтому всё, что он делает, происходит за пределами песочницы. Именно поэтому--allow-run=denoособенно опасен: скрипт, способный запустить новый процессdeno, может запустить его с--allow-all, не унаследовав никаких ограничений родительского процесса и полностью выйдя из песочницы. Предоставляйте--allow-runтолько для конкретных доверенных исполняемых файлов, если это возможно (--allow-run=git), и не давайте программе в песочнице возможность запускатьdenoили оболочку. -
--allow-ffiзагружает нативные библиотеки через FFI или дополнения Node-API. Deno обеспечивает соблюдение разрешений на уровне JavaScript, но нативная библиотека выполняется как скомпилированный машинный код в том же процессе и может напрямую вызывать системные вызовы. После загрузки она может читать файлы, открывать сокеты или делать всё, что позволяет операционная система этому процессу, независимо от того, какие флаги--allow-*вы передали.
Будьте осторожны при сочетании --allow-write с --allow-run. Разрешение на запись в каталог, содержащий разрешённый исполняемый файл (или в сам исполняемый файл), позволяет программе перезаписать этот файл, поэтому следующий запущенный ею дочерний процесс выполнит код, контролируемый злоумышленником, с привилегиями хоста. Не предоставляйте доступ на запись к каталогам, в которых находятся бинарные файлы, разрешённые с помощью --allow-run.
Принимая решение о том, можно ли доверять выполняемому коду, считайте каждое из этих разрешений эквивалентным --allow-all.
Изменение разрешений во время выполнения
Программа может проверять и ограничивать собственные разрешения с помощью API Deno.permissions. Метод Deno.permissions.query сообщает, предоставлено ли разрешение, метод Deno.permissions.request запрашивает разрешение по мере необходимости, а метод Deno.permissions.revoke возвращает предоставленное разрешение в состояние запроса:
// Read a config file at startup, then give up read access.
const config = JSON.parse(await Deno.readTextFile("./config.json"));
await Deno.permissions.revoke({ name: "read" });
// Later reads now have to prompt again, and fail outright under --no-prompt.
Отзыв разрешений, которые больше не нужны после запуска, — простой способ уменьшить поверхность атаки, доступную остальному коду, включая зависимости.
Выполнение кода
Deno не накладывает ограничений на выполнение кода с тем же уровнем привилегий. Это означает, что код, выполняющийся в среде Deno, может использовать eval, new Function или даже динамический импорт либо веб-воркеры для выполнения произвольного кода с тем же уровнем привилегий, что и у кода, вызвавшего eval, new Function, динамический импорт или веб-воркер.
Этот код может находиться в сети, в локальном файле (если предоставлены разрешения на чтение) или быть сохранён в виде обычного текста в строке внутри кода, вызвавшего eval, new Function, динамический импорт или веб-воркер.
Выполнение недоверенного кода
Хотя в Deno предусмотрены средства безопасности, призванные защитить хост-компьютер и сеть от вреда, недоверенный код всё равно представляет опасность. При выполнении недоверенного кода важно использовать несколько уровней защиты. Ниже приведены некоторые рекомендации по выполнению недоверенного кода; мы советуем следовать им всем при выполнении произвольного недоверенного кода:
- Запускайте
denoс ограниченными разрешениями и заранее определяйте, какой именно код должен выполняться (а также предотвращайте загрузку дополнительного кода с помощью lockfile--frozenи--cached-only). - Изолируйте недоверенную часть в веб-воркере с сокращённым набором разрешений, чтобы он не мог унаследовать все разрешения, предоставленные основной программе.
- Используйте предоставляемые ОС механизмы изоляции, такие как
chroot,cgroups,seccompи т. д. - Используйте изолированную среду, например VM или MicroVM (gVisor, Firecracker и т. д.).
Аудит зависимостей
Песочница разрешений контролирует действия кода во время выполнения, но не сообщает, содержат ли ваши зависимости известные уязвимости. В Deno есть deno audit для проверки зависимостей по базам данных уязвимостей; этот инструмент удобно использовать в качестве контрольного этапа CI. Информацию о долгосрочной защите зависимостей см. в разделе управление цепочкой поставок.
Брокер разрешений
Использование брокера разрешений меняет принцип принятия решений в Deno: флаги CLI и запросы больше не применяются. Прежде чем включать DENO_PERMISSION_BROKER_PATH, убедитесь, что брокер надёжен, прошёл аудит и доступен.
Для централизованного принятия решений по разрешениям на основе политик Deno может передавать все проверки разрешений внешнему процессу-брокеру. Чтобы включить эту возможность, задайте переменную окружения DENO_PERMISSION_BROKER_PATH, указав путь, по которому Deno будет подключаться к брокеру:
- В Unix-подобных системах: путь к доменному сокету Unix (например,
/tmp/deno-perm.sock). - В Windows: именованный канал (например,
\\.\pipe\deno-perm-broker).
Когда брокер разрешений активен:
- Все флаги
--allow-*и--deny-*игнорируются. - Интерактивные запросы разрешений не отображаются (режим эквивалентен неинтерактивному).
- Каждая проверка разрешения отправляется брокеру; брокер должен принять решение по каждому запросу.
Если при работе брокера что-то пойдёт не так (например, Deno не сможет подключиться к сокету/каналу, сообщения окажутся некорректными, придут не по порядку, их идентификаторы не совпадут или соединение неожиданно закроется), Deno немедленно завершит процесс, чтобы сохранить целостность и предотвратить повышение привилегий.
Форматы сообщений запроса и ответа версионируются и описываются схемами JSON:
- Схема запроса: permission-broker-request.v1.json
- Схема ответа: permission-broker-response.v1.json
Каждый запрос содержит версию (v), идентификатор процесса Deno (pid), уникальный монотонно возрастающий идентификатор запроса id, временную метку (datetime, RFC 3339), название разрешения permission и необязательный параметр value, зависящий от типа разрешения. Ответ должен содержать тот же id и включать result со значением либо "allow", либо "deny". В случае отказа можно также добавить понятное человеку сообщение reason.
Пример обмена сообщениями:
-> req {"v":1,"pid":10234,"id":1,"datetime":"2025-01-01T00:00:00.000Z","permission":"read","value":"./run/permission_broker/scratch.txt"}
<- res {"id":1,"result":"allow"}
-> req {"v":1,"pid":10234,"id":2,"datetime":"2025-01-01T00:00:01.000Z","permission":"read","value":"./run/permission_broker/scratch.txt"}
<- res {"id":2,"result":"allow"}
-> req {"v":1,"pid":10234,"id":3,"datetime":"2025-01-01T00:00:02.000Z","permission":"read","value":"./run/permission_broker/log.txt"}
<- res {"id":3,"result":"allow"}
-> req {"v":1,"pid":10234,"id":4,"datetime":"2025-01-01T00:00:03.000Z","permission":"write","value":"./run/permission_broker/log.txt"}
<- res {"id":4,"result":"allow"}
-> req {"v":1,"pid":10234,"id":5,"datetime":"2025-01-01T00:00:04.000Z","permission":"env","value":null}
<- res {"id":5,"result":"deny","reason":"Environment access is denied."}
Последнее обновление:
© 2018–2025 the Deno authors
Licensed under the MIT License.
https://docs.deno.com/runtime/fundamentals/security