Перейти к содержанию

Разрешения

v18.x.x

Разрешения могут быть использованы для управления тем, к каким системным ресурсам имеет доступ процесс Node.js или какие действия он может выполнять с этими ресурсами. Разрешения также могут управлять тем, к каким модулям могут обращаться другие модули.

  • Module-based permissions контролирует, какие файлы или URL доступны другим модулям во время выполнения приложения. Это можно использовать, например, для контроля того, к каким модулям могут обращаться сторонние зависимости.

  • Process-based permissions контролируют доступ процесса Node.js к ресурсам. Ресурс может быть полностью разрешен или запрещен, или могут контролироваться действия, связанные с ним. Например, чтение файловой системы может быть разрешено, а запись запрещена.

Если вы обнаружили потенциальную уязвимость безопасности, обратитесь к нашей Политике безопасности.

Разрешения на основе модулей

Политики

Стабильность: 1 – Экспериментальная

Фича изменяется и не допускается флагом командной строки. Может быть изменена или удалена в последующих версиях.

Node.js содержит экспериментальную поддержку для создания политик загрузки кода.

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

Лучшей практикой будет обеспечение того, что манифест политики доступен только для чтения для запущенного приложения Node.js и что файл не может быть изменен запущенным приложением Node.js каким-либо образом. Типичная настройка заключается в создании файла политики под именем пользователя, отличным от имени пользователя, запускающего Node.js, и предоставлении прав на чтение пользователю, запускающему Node.js.

Включение

Флаг --experimental-policy может быть использован для включения возможностей политик при загрузке модулей.

После установки этого флага все модули должны соответствовать файлу манифеста политики, переданному с флагом:

1
node --experimental-policy=policy.json app.js

Манифест политики будет использоваться для наложения ограничений на код, загружаемый Node.js.

Для предотвращения несанкционированного доступа к файлам политик на диске, целостность самого файла политики может быть обеспечена через --policy-integrity. Это позволяет запустить node и подтвердить содержимое файла политики, даже если файл изменен на диске.

1
node --experimental-policy=policy.json --policy-integrity="sha384-SggXRQHwCG8g+DktYYzxkXRIkTiEYWBHqev0xnpCxYlqMBufKZHAHQM3/boDaI/0" app.js

Особенности

Поведение при ошибках

Когда проверка политики не удается, Node.js по умолчанию выдает ошибку. Можно изменить поведение ошибки на одно из нескольких возможных, определив поле "onerror" в манифесте политики. Для изменения поведения доступны следующие значения:

  • exit: немедленно завершает процесс. Никакой код очистки не будет запущен.
  • log: будет регистрировать ошибку в месте сбоя.
  • throw: приведет к выбросу JS-ошибки в месте сбоя. Это значение используется по умолчанию.
1
2
3
4
5
6
7
8
{
    "onerror": "log",
    "resources": {
        "./app/checked.js": {
            "integrity": "sha384-SggXRQHwCG8g+DktYYzxkXRIkTiEYWBHqev0xnpCxYlqMBufKZHAHQM3/boDaI/0"
        }
    }
}
Проверки целостности

Файлы политики должны использовать проверки целостности со строками Subresource Integrity, совместимыми с атрибутом целостности браузера, связанным с абсолютными URL.

При использовании require() или import все ресурсы, участвующие в загрузке, проверяются на целостность, если был указан манифест политики. Если ресурс не соответствует целостности, указанной в манифесте, будет выдана ошибка.

Пример файла политики, который разрешает загрузку файла checked.js:

1
2
3
4
5
6
7
{
    "resources": {
        "./app/checked.js": {
            "integrity": "sha384-SggXRQHwCG8g+DktYYzxkXRIkTiEYWBHqev0xnpCxYlqMBufKZHAHQM3/boDaI/0"
        }
    }
}

Каждый ресурс, указанный в манифесте политики, может иметь один из следующих форматов для определения его местоположения:

  1. relative-URL string на ресурс из манифеста, например ./resource.js, ../resource.js или /resource.js.
  2. Полная строка 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, чтобы принять любое тело для ресурса, что может быть полезно для локальной разработки. Это не рекомендуется использовать в производстве, так как это позволит неожиданно изменять ресурсы, чтобы они считались действительными.

Перенаправление зависимостей

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
{
    "resources": {
        "./app/checked.js": {
            "dependencies": {
                "fs": true,
                "os": "./app/node_modules/alt-os",
                "http": { "import": true }
            }
        }
    }
}

Зависимости определяются запрошенной строкой спецификатора и имеют значения либо true, null, либо строку, указывающую на разрешаемый модуль, либо объект условий.

Строка спецификатора не выполняет никакого поиска и должна точно соответствовать тому, что предоставляется в require() или import, за исключением шага канонизации. Поэтому в политике может потребоваться несколько спецификаторов, если она использует несколько разных строк для указания на один и тот же модуль (например, исключение расширения).

Строки спецификаторов канонизируются, но не разрешаются перед использованием для сопоставления, чтобы иметь некоторую совместимость с картами импорта, например, если ресурс file:///C:/app/server.js получил следующее перенаправление из политики, расположенной по адресу file:///C:/app/policy.json:

1
2
3
4
5
6
7
8
9
{
    "resources": {
        "file:///C:/app/utils.js": {
            "dependencies": {
                "./utils.js": "./utils-v2.js"
            }
        }
    }
}

Любой спецификатор, используемый для загрузки file:///C:/app/utils.js, будет перехвачен и перенаправлен на file:///C:/app/utils-v2.js, независимо от использования абсолютного или относительного спецификатора. Однако, если используется спецификатор, не являющийся абсолютной или относительной строкой URL, он не будет перехвачен. Так, если используется импорт, например import('#utils'), он не будет перехвачен.

Если значение перенаправления равно true, будет использоваться поле "dependencies" в верхней части файла политики. Если это поле в верхней части файла политики равно true, то для поиска модуля используются алгоритмы поиска узлов по умолчанию.

Если значением перенаправления является строка, она разрешается относительно манифеста и затем немедленно используется без поиска.

Любая строка-спецификатор, для которой предпринимается попытка разрешения и которая не указана в зависимостях, приводит к ошибке в соответствии с политикой.

Перенаправление не препятствует доступу к API через такие средства, как прямой доступ к require.cache или через module.constructor, которые позволяют получить доступ к загружаемым модулям. Перенаправление политики влияет только на спецификаторы require() и import. Другие средства, такие как предотвращение нежелательного доступа к API через переменные, необходимы для блокировки этого пути загрузки модулей.

Булево значение true для карты зависимостей может быть указано, чтобы позволить модулю загружать любой спецификатор без перенаправления. Это может быть полезно для локальной разработки и может иметь некоторое применение в продакшене, но должно использоваться только с осторожностью после аудита модуля, чтобы убедиться в правильности его поведения.

Подобно "exports" в package.json, зависимости также могут быть указаны как объекты, содержащие условия, которые определяют, как загружаются зависимости. В предыдущем примере, "http" разрешено, если условие "import" является частью загрузки.

Значение null для разрешаемого значения приводит к неудаче разрешения. Это может быть использовано для того, чтобы обеспечить явное предотвращение некоторых видов динамического доступа.

Неизвестные значения для разрешаемого расположения модуля вызывают сбои, но не гарантируют совместимость с прямыми ссылками.

Пример: Перенаправленная зависимость

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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();
    }
};

Scopes

Используйте поле "scopes" в манифесте, чтобы задать конфигурацию сразу для многих ресурсов. Поле "scopes" работает путем сопоставления ресурсов по их сегментам. Если область видимости или ресурс включает "cascade": true, неизвестные спецификаторы будут искаться в содержащей их области видимости. Содержащая область видимости для каскадирования находится путем рекурсивного сокращения 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: для своей области видимости протокола, поскольку они не принимают происхождение.

Пример
1
2
3
4
5
6
7
{
    "scopes": {
        "file:///C:/app/": {},
        "file:": {},
        "": {}
    }
}

Если файл расположен по адресу file:///C:/app/bin/main.js, то будут проверены следующие области видимости в порядке убывания:

  1. "file:///C:/app/bin/".

    Это определяет политику для всех файловых ресурсов в пределах "file:///C:/app/bin/". Этого поля нет в поле "scopes" политики и оно будет пропущено. Добавление этого поля в политику приведет к тому, что оно будет использоваться до поля "file:///C:/app/".

  2. "file:///C:/app/".

    Определяет политику для всех файловых ресурсов в пределах "file:///C:/app/". Это находится в поле "scopes" политики и определяет политику для ресурса по адресу file:///C:/app/bin/main.js. Если область видимости имеет значение "cascade": true, любые неудовлетворенные запросы к ресурсу будут передаваться следующей соответствующей области видимости для file:///C:/app/bin/main.js, "file:".

  3. "file:///C:/".

    Определяет политику для всех файловых ресурсов в пределах "file:///C:/". Это поле отсутствует в поле "scopes" политики и будет пропущено. Он не будет использоваться для file:///C:/app/bin/main.js, если только "file:///" не установлен на каскад или не находится в поле "scopes" политики.

  4. "file:///".

    Определяет политику для всех файловых ресурсов на localhost. Это поле отсутствует в поле "scopes" политики и будет пропущено. Он не будет использоваться для file:///C:/app/bin/main.js, если "file:///" не установлен в значение cascade или не находится в поле "scopes" политики.

  5. "file:".

    Определяет политику для всех ресурсов, основанных на файлах. Она не будет использоваться для file:///C:/app/bin/main.js, если "file:///" не установлено значение cascade или отсутствует в "scopes" политики.

  6. ""

    Определяет политику для всех ресурсов. Она не будет использоваться для file:///C:/app/bin/main.js, если только для "file:" не установлено значение cascade.

Целостность с помощью диапазонов

Установка целостности в true для области видимости установит целостность для любого ресурса, не найденного в манифесте, в true.

Установка целостности в значение null для области видимости установит целостность для любого ресурса, не найденного в манифесте, как несоответствующую.

Не включать целостность - это то же самое, что установить целостность в null.

Каскад" для проверок целостности будет игнорироваться, если явно задана целостность.

Следующий пример позволяет загрузить любой файл:

1
2
3
4
5
6
7
{
    "scopes": {
        "file:": {
            "целостность": true
        }
    }
}
Перенаправление зависимостей с помощью диапазонов

Следующий пример разрешает доступ к fs для всех ресурсов в пределах ./app/:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
{
    "resources": {
        "./app/checked.js": {
            "каскад": true,
            "целостность": true
        }
    },
    "scopes": {
        "./app/": {
            "dependencies": {
                "fs": true
            }
        }
    }
}

Следующий пример разрешает доступ к fs для всех data: ресурсов:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
{
    "resources": {
        "data:text/javascript,import('node:fs');": {
            "каскад": true,
            "целостность": true
        }
    },
    "scopes": {
        "data:": {
            "dependencies": {
                "fs": true
            }
        }
    }
}
Пример: карты импорта эмуляция

Дана карта импорта:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{
  "imports": {
    "react": "./app/node_modules/react/index.js"
  },
  "scopes": {
    "./ssr/": {
      "react": "./app/node_modules/server-side-react/index.js".
    }
  }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
{
    "dependencies": true,
    "scopes": {
        "": {
            "каскад": true,
            "dependencies": {
                "react": "./app/node_modules/react/index.js"
            }
        },
        "./ssr/": {
            "cascade": true,
            "dependencies": {
                "react": "./app/node_modules/server-side-react/index.js"
            }
        }
    }
}

Карты импорта предполагают, что по умолчанию вы можете получить любой ресурс. Это означает, что dependencies на верхнем уровне политики должны быть установлены в true. Политики требуют, чтобы это было опционально, поскольку это позволяет всем ресурсам приложения иметь перекрестную связь, что не имеет смысла для многих сценариев. Они также предполагают, что любой данный диапазон имеет доступ к любому диапазону выше его разрешенных зависимостей; все диапазоны, эмулирующие карты импорта, должны установить "cascade": true.

Карты импорта имеют только одну область видимости верхнего уровня для своих "импортов". Поэтому для эмуляции импортов используйте область видимости "". Для эмуляции "scopes" используйте "scopes" аналогично тому, как "scopes" работает в картах импорта.

Предостережения: Политики не используют сопоставление строк для поиска различных областей видимости. Они используют обход URL. Это означает, что такие вещи как blob: и data: URL могут быть не полностью совместимы между двумя системами. Например, карты импорта могут частично соответствовать data: или blob: URL путем разбиения URL на символы /, а политики намеренно не могут. Для blob: URL диапазоны карт импорта не принимают происхождение blob: URL.

Кроме того, карты импорта работают только с import, поэтому может быть желательно добавить условие "import" ко всем отображениям зависимостей.

Разрешения на основе процесса

Модель разрешения

Стабильность: 1 – Экспериментальная

Фича изменяется и не допускается флагом командной строки. Может быть изменена или удалена в последующих версиях.

Модель разрешений Node.js - это механизм для ограничения доступа к определенным ресурсам во время выполнения. API существует за флагом --experimental-permission, который при включении ограничивает доступ ко всем доступным разрешениям.

Доступные разрешения документируются флагом --experimental-permission.

При запуске Node.js с --experimental-permission, возможность доступа к файловой системе, порождения процессов и использования node:worker_threads будет ограничена.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
$ node --experimental-permission index.js
node:internal/modules/cjs/loader:171
  const result = internalModuleStat(filename);
                 ^

Error: Access to this API has been restricted
    at stat (node:internal/modules/cjs/loader:171:18)
    at Module._findPath (node:internal/modules/cjs/loader:627:16)
    at resolveMainPath (node:internal/modules/run_main:19:25)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:76:24)
    at node:internal/main/run_main_module:23:47 {
  code: 'ERR_ACCESS_DENIED',
  permission: 'FileSystemRead'
}

Разрешение доступа к порождению процесса и созданию рабочих потоков может быть сделано с помощью --allow-child-process и --allow-worker соответственно.

Runtime API

При включении модели разрешений с помощью флага --experimental-permission к объекту process добавляется новое свойство permission. Это свойство содержит две функции:

permission.deny(scope [,parameters])

Вызов API для запрета разрешений во время выполнения (permission.deny())

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
process.permission.deny('fs'); // Запрещаем разрешения на ВСЕ операции с файлами.

// Запретить разрешения на ВСЕ операции FileSystemWrite
process.permission.deny('fs.write');
// Запретить разрешения FileSystemWrite для защищенной папки
process.permission.deny('fs.write', [
    '/home/rafaelgss/protected-folder',
]);
// Запретить разрешения на ВСЕ операции чтения файловой системы
process.permission.deny('fs.read');
// Запретить разрешения FileSystemRead для защищенной папки
process.permission.deny('fs.read', [
    '/home/rafaelgss/protected-folder',
]);
permission.has(scope ,parameters)

Вызов API для проверки разрешений во время выполнения (permission.has())

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
process.permission.has('fs.write'); // true
process.permission.has(
    'fs.write',
    '/home/rafaelgss/protected-folder'
); // true

process.permission.deny(
    'fs.write',
    '/home/rafaelgss/protected-folder'
);

process.permission.has('fs.write'); // true
process.permission.has(
    'fs.write',
    '/home/rafaelgss/protected-folder'
); // false

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

Чтобы разрешить доступ к файловой системе, используйте флаги --allow-fs-read и --allow-fs-write:

1
2
3
4
$ 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)

Действительными аргументами для обоих флагов являются:

  • * - Разрешить все операции в заданной области видимости (чтение/запись).
  • Пути, разделенные запятой (,) для управления операциями чтения/записи.

Пример:

  • --allow-fs-read=* - Разрешит все операции FileSystemRead.
  • --allow-fs-write=* - Разрешит все операции FileSystemWrite.
  • --allow-fs-write=/tmp/ - Разрешит FileSystemWrite доступ к папке /tmp/.
  • --allow-fs-read=/tmp/,/home/.gitignore - Разрешает FileSystemRead доступ к папке /tmp/ и пути /home/.gitignore.

Также поддерживаются подстановочные знаки:

  • --allow-fs-read:/home/test* разрешит доступ на чтение ко всему, что соответствует подстановочному символу. Например: /home/test/file1 или /home/test2.

Существуют ограничения, которые необходимо знать перед использованием этой системы:

  • Нативные модули ограничены по умолчанию при использовании модели разрешений.

  • Относительные пути не поддерживаются через CLI (--allow-fs-*). API времени выполнения поддерживает относительные пути.

  • Модель не наследуется в процессе дочернего узла.

  • Модель не наследуется в рабочем потоке.

  • При создании симлинков цель (первый аргумент) должна иметь доступ на чтение и запись.

  • Изменения разрешений не применяются задним числом к существующим ресурсам. Рассмотрим следующий фрагмент:

1
2
3
4
5
6
7
8
const fs = require('node:fs');

// Открываем fd
const fd = fs.openSync('./README.md', 'r');
// Затем запретите доступ ко всем операциям чтения fs.read
process.permission.deny('fs.read');
// Этот вызов НЕ завершится неудачей, и файл будет прочитан
const data = fs.readFileSync(fd);

Поэтому, когда это возможно, применяйте правила разрешений перед любым оператором:

1
2
3
process.permission.deny('fs.read');
const fd = fs.openSync('./README.md', 'r');
// Ошибка: Доступ к этому API был ограничен

Комментарии