
Изоляция и инкапсуляция защищённых DOM-деревьев с использованием ShadowDOM
⚠️ ЭКСПЕРИМЕНТАЛЬНО [WIP] - ИСПОЛЬЗУЙТЕ НА СВОЙ РИСК (подробнее)
Попробуйте LavaDome - посетите демо-приложение, откройте консоль и сделайте всё возможное, чтобы украсть секрет из экземпляра LavaDome (сообщите о своём успехе)
В современных веб-стандартах нет устоявшегося способа выборочно изолировать поддеревья DOM безопасным образом. Другими словами, мы не можем контролировать доступ к разделам DOM, предоставляя доступ одним сторонам и блокируя его для других, если они используют одну и ту же среду выполнения JavaScript.
Мы живём в мире, где больше не можем доверять коду в наших собственных приложениях, и выполнение в одном origin не гарантирует безопасность. Чтобы защитить секреты во фронтенде, мы должны иметь возможность показывать контент пользователю, гарантируя, что он не может быть скомпрометирован JavaScript-кодом, работающим в том же origin.

В настоящее время этот чувствительный контент просто присоединяется к DOM после экспорта, что делает его полностью доступным для всех сущностей, работающих в том же приложении. То есть части кода, которые не должны иметь доступ к приватному ключу, могут легко извлечь его в открытом виде, если вредоносный код имеет доступ к DOM.
Но не волнуйтесь. Мы считаем, что это решаемая проблема 👇
LavaDome в настоящее время поддерживает Vanilla JavaScript и React (в планах — больше)
import { LavaDome as LavaDomeJavaScript } from '@lavamoat/lavadome-javascript';
const root = document.getElementById('root'); const lavadome = new LavaDomeJavaScript(root); lavadome.text(secret); lavadome.copy(); // copy to clipboard
### [React](https://github.com/lavamoat/lavadome/blob/main/packages/react)```javascript
import { LavaDome as LavaDomeReact, toLavaDomeToken } from '@lavamoat/lavadome-react';
function Secret({ text }) {
const {token, copy} = toLavaDomeCapabilities(text);
return <>
<a onClick={copy}> copy to clipboard </a>
<LavaDomeReact token={token} />
</>;
}
В дополнение к корневому узлу, все конструкторы принимают необязательный второй аргумент options:```javascript // javascript new LavaDomeJavaScript(root, { // boolean unsafeOpenModeShadow: false, });
// react function Secret({ text }) { const {token} = toLavaDomeCapabilities(text); return <LavaDomeReact token={token} // boolean unsafeOpenModeShadow={false} /> }
### Безопасное использование
Из-за ограничений веб-ядра, чтобы безопасно интегрировать LavaDome, нужно учитывать несколько моментов, требующих активных действий со стороны разработчика, выполняющего интеграцию:
#### Порядок выполнения
LavaDome, как и любое другое программное обеспечение для безопасности на JavaScript, всегда уязвим для кода, выполняемого до его загрузки.
Это означает, что, за исключением кода, которому мы абсолютно доверяем, LavaDome должен быть первым фрагментом кода, загружаемым в программу веб-приложения.
Хотя это не означает, что разработчик должен использовать его немедленно (а только тогда, когда это необходимо), он всё же обязан подключить программу как можно раньше.
Чтобы сделать это правильно (безопасно), он должен быть первым объявлением import/require во всей программе:```javascript
import '@lavamoat/lavadome-react';
import 'other-stuff';
console.log('Program starts here');
Таким образом мы гарантируем, что LavaDome успеет подготовиться к безопасному использованию.
Обратите внимание, что это относится и к остальным пакетам LavaDome, а не только к @lavamoat/lavadome-react (поэтому импортировать более одного из них необязательно).
Перейдите к разделу Безопасность(defensive-coding), чтобы узнать больше.
Из-за атак по побочным каналам и ограничений веб-платформы импорт удалённых шрифтов может стать успешным методом атаки на LavaDome. Поскольку это встроено в область CSS, решить эту проблему средствами LavaDome на данный момент невозможно.
К счастью, это можно эффективно решить с помощью директивы font-src CSP.
Чтобы снизить риск такой атаки, убедитесь, что ваше веб-приложение не разрешает загрузку шрифтов с неизвестных серверов.
Перейдите к разделу Безопасность(side-channeling), чтобы узнать больше.
Текст, передаваемый разработчиком в LavaDome, должен быть на 100% непредсказуемым, иначе он может быть атакован и похищен.
Так, если ваше приложение должно отображать "your key is 234789", это означает, что структура DOM должна быть:```html
your key is 234789
и не должно быть:```html
<span> <lavadome>your key is 234789</lavadome> </span>
Переходите к Безопасность(findability), чтобы узнать больше.
Интеграция LavaDome может оказаться непростой при тестировании: поскольку LavaDome отлично скрывает секрет, он точно так же хорошо скрывает его и от ваших тестов!
Чтобы успешно интегрировать LavaDome в вашу тестовую среду, вам может понадобиться помощь LavaDomeDebug, который экспортируется из @lavamoat/lavadome-core:```javascript
// IMPORT/USE FOR TESTING/DEBUGGING PURPOSES ONLY - NEVER IN PRODUCTION!
import { LavaDomeDebug } from '@lavamoat/lavadome-core';
Вот некоторые из отладочных методов-утилит, которые экспортирует `LavaDomeDebug` и которые могут помочь вам при тестировании компонентов на основе `LavaDome`:
#### `getTextByRoot()`
Получив корневой элемент, к которому подключён `LavaDome`, `getTextByRoot()` рекурсивно извлечёт и восстановит внутренний секрет. Чтобы это стало возможным, экземпляр `LavaDome` должен быть изначально создан с небезопасным параметром `@unsafeOpenModeShadow`, который делает внутренние тени `LavaDome` доступными извне.
Разумеется, это НЕБЕЗОПАСНО и делает `LavaDome` полностью уязвимым, однако имеет смысл использовать только в целях тестирования/отладки — обязательно убедитесь, что этот параметр никогда не включён в production!```javascript
new LavaDomeJavaScript(root, {
unsafeOpenModeShadow: isThisTestingEnv, // boolean
}).text('123456');
LavaDomeDebug.getTextByRoot(root) === '123456'; // true
stripDistractionFromText()При использовании веб-драйверов для тестирования и указании им извлекать внутренний текст корневого элемента экземпляра LavaDome, они возвращают строку, содержащую как секрет, так и отвлекающий текст LavaDome.