Система расширений Chrome по умолчанию применяет довольно строгую политику безопасности контента (CSP) . Ограничения политики просты: скрипт должен быть перемещен в отдельные файлы JavaScript, встроенные обработчики событий должны быть преобразованы для использования addEventListener , а eval() отключена.
Однако мы признаем, что многие библиотеки используют eval() и eval конструкции, такие как new Function() для оптимизации производительности и упрощения выражений. Библиотеки шаблонизации особенно подвержены такому стилю реализации. Хотя некоторые (например, Angular.js ) поддерживают CSP из коробки, многие популярные фреймворки еще не обновились до механизма, совместимого с миром расширений без использования eval . Поэтому удаление поддержки этой функциональности оказалось более проблематичным для разработчиков, чем ожидалось .
В этом документе представлена функция «песочницы» как безопасный механизм для включения этих библиотек в ваши проекты без ущерба для безопасности.
Зачем нужна песочница?
Использование eval внутри расширения опасно, поскольку выполняемый им код имеет доступ ко всему содержимому среды с высокими правами доступа. Существует множество мощных API-интерфейсов chrome.* , которые могут серьезно повлиять на безопасность и конфиденциальность пользователя; простая утечка данных — это наименьшая из наших проблем. Предлагаемое решение — это песочница, в которой eval может выполнять код без доступа ни к данным расширения, ни к его высокоценным API-интерфейсам. Нет данных, нет API, нет проблем.
Мы достигаем этого, указывая в пакете расширения конкретные HTML-файлы как находящиеся в изолированной среде. При загрузке изолированной страницы она перемещается на уникальный источник и ей запрещается доступ к chrome.* . Если мы загрузим эту изолированную страницу в наше расширение через iframe , мы сможем передавать ей сообщения, позволять ей обрабатывать эти сообщения и ждать ответа. Этот простой механизм обмена сообщениями предоставляет нам все необходимое для безопасного включения кода, управляемого функцией eval , в рабочий процесс нашего расширения.
Создайте и используйте песочницу.
Если вы хотите сразу же приступить к коду, скачайте расширение с примером песочницы и приступайте . Это рабочий пример небольшого API для обмена сообщениями, построенного на основе библиотеки шаблонов Handlebars , и он должен предоставить вам все необходимое для начала работы. Для тех, кому нужны более подробные объяснения, давайте вместе рассмотрим этот пример.
Список файлов в манифесте
Каждый файл, который должен запускаться в изолированной среде, необходимо указать в манифесте расширения, добавив свойство ` sandbox . Это критически важный шаг, о котором легко забыть, поэтому дважды проверьте, что файл, запускаемый в изолированной среде, указан в манифесте. В этом примере мы запускаем в изолированной среде файл с оригинальным именем "sandbox.html". Запись в манифесте выглядит следующим образом:
{
...,
"sandbox": {
"pages": ["sandbox.html"]
},
...
}
Загрузите файл из песочницы.
Чтобы сделать что-то интересное с изолированным файлом, нам нужно загрузить его в контекст, где к нему сможет обратиться код расширения. В данном случае файл sandbox.html загружен на страницу расширения с помощью iframe . JavaScript-файл страницы содержит код, который отправляет сообщение в песочницу всякий раз, когда нажимается действие браузера, находя iframe на странице и вызывая postMessage() для его contentWindow . Сообщение представляет собой объект, содержащий три свойства: context , templateName и command . Мы подробнее рассмотрим context и command чуть позже.
service-worker.js:
chrome.action.onClicked.addListener(() => {
chrome.tabs.create({
url: 'mainpage.html'
});
console.log('Opened a tab with a sandboxed page!');
});
extension-page.js:
let counter = 0;
document.addEventListener('DOMContentLoaded', () => {
document.getElementById('reset').addEventListener('click', function () {
counter = 0;
document.querySelector('#result').innerHTML = '';
});
document.getElementById('sendMessage').addEventListener('click', function () {
counter++;
let message = {
command: 'render',
templateName: 'sample-template-' + counter,
context: { counter: counter }
};
document.getElementById('theFrame').contentWindow.postMessage(message, '*');
});
Совершить что-нибудь опасное
При загрузке файла sandbox.html загружается библиотека Handlebars, которая создает и компилирует встроенный шаблон так, как это предлагает Handlebars:
extension-page.html:
<!DOCTYPE html>
<html>
<head>
<script src="mainpage.js"></script>
<link href="styles/main.css" rel="stylesheet" />
</head>
<body>
<div id="buttons">
<button id="sendMessage">Click me</button>
<button id="reset">Reset counter</button>
</div>
<div id="result"></div>
<iframe id="theFrame" src="sandbox.html" style="display: none"></iframe>
</body>
</html>
sandbox.html:
<script id="sample-template-1" type="text/x-handlebars-template">
<div class='entry'>
<h1>Hello</h1>
<p>This is a Handlebar template compiled inside a hidden sandboxed
iframe.</p>
<p>The counter parameter from postMessage() (outer frame) is:
</p>
</div>
</script>
<script id="sample-template-2" type="text/x-handlebars-template">
<div class='entry'>
<h1>Welcome back</h1>
<p>This is another Handlebar template compiled inside a hidden sandboxed
iframe.</p>
<p>The counter parameter from postMessage() (outer frame) is:
</p>
</div>
</script>
Это не приводит к ошибке! Хотя Handlebars.compile в итоге использует new Function , всё работает точно так, как ожидалось, и в итоге мы получаем скомпилированный шаблон в templates['hello'] .
Передайте результат обратно.
Мы сделаем этот шаблон доступным для использования, настроив обработчик сообщений, который будет принимать команды со страницы расширения. Мы будем использовать переданную command , чтобы определить, что должно быть сделано (можно представить себе не только рендеринг; возможно, создание шаблонов? Возможно, управление ими каким-то образом?), а context будет передан непосредственно в шаблон для рендеринга. Сгенерированный HTML будет передан обратно на страницу расширения, чтобы расширение могло позже использовать его для каких-либо полезных целей:
<script>
const templatesElements = document.querySelectorAll(
"script[type='text/x-handlebars-template']"
);
let templates = {},
source,
name;
// precompile all templates in this page
for (let i = 0; i < templatesElements.length; i++) {
source = templatesElements[i].innerHTML;
name = templatesElements[i].id;
templates[name] = Handlebars.compile(source);
}
// Set up message event handler:
window.addEventListener('message', function (event) {
const command = event.data.command;
const template = templates[event.data.templateName];
let result = 'invalid request';
// if we don't know the templateName requested, return an error message
if (template) {
switch (command) {
case 'render':
result = template(event.data.context);
break;
// you could even do dynamic compilation, by accepting a command
// to compile a new template instead of using static ones, for example:
// case 'new':
// template = Handlebars.compile(event.data.templateSource);
// result = template(event.data.context);
// break;
}
} else {
result = 'Unknown template: ' + event.data.templateName;
}
event.source.postMessage({ result: result }, event.origin);
});
</script>
Вернувшись на страницу расширения, мы получим это сообщение и сможем кое-что интересное сделать с переданными нам html данными. В данном случае мы просто выведем их через уведомление , но вполне возможно безопасно использовать этот HTML-код в пользовательском интерфейсе расширения. Вставка его через innerHTML не представляет существенного риска для безопасности, поскольку мы доверяем контенту, который был отображен в песочнице.
Этот механизм упрощает создание шаблонов, но, конечно, он не ограничивается только ими. Любой код, который не работает «из коробки» при строгой политике безопасности контента, можно изолировать; на самом деле, часто полезно изолировать компоненты ваших расширений, которые будут работать корректно, чтобы ограничить каждую часть вашей программы минимальным набором привилегий, необходимых для ее правильного выполнения. Презентация « Создание безопасных веб-приложений и расширений Chrome» с Google I/O 2012 содержит несколько хороших примеров применения этих методов на практике и стоит потраченных 56 минут вашего времени.