Chrome 的扩展程序系统会强制执行相当严格的默认内容安全政策 (CSP)。
政策限制非常简单:脚本必须移出线,放入单独的 JavaScript 文件中,内嵌事件处理脚本必须转换为使用 addEventListener
,并且 eval()
处于停用状态。
不过,我们也了解到,许多库都使用 eval()
和 eval
类构造(例如 new Function()
)来优化性能和简化表达。模板库特别容易出现这种实现方式。虽然某些环境(如 Angular.js)支持 CSP out
许多热门框架尚未更新为与 Android Studio 兼容的
扩展程序的少了 eval
的世界。因此,我们发现移除对该功能的支持对开发者而言造成的问题比预期多。
本文档介绍了沙盒作为一种安全机制,可让您在项目中添加这些库,而不会影响安全性。
为什么要使用沙盒?
在扩展程序中使用 eval
是危险的,因为它执行的代码可以访问扩展程序的高权限环境中的所有内容。提供了大量功能强大的 chrome.*
API,
严重影响用户的安全和隐私;我们最担心的是简单的数据渗漏问题。
提供的解决方案是一个沙盒,在此沙盒中,eval
无需访问
或扩展程序的高价值 API。没有数据、不使用 API,也没有关系。
为此,我们会将扩展程序软件包内的特定 HTML 文件列为已沙盒化。
每当有沙盒机制加载网页时,系统都会将其移动到唯一来源,并会被拒绝
对 chrome.*
API 的访问权限。如果通过 iframe
将此沙盒化页面加载到扩展程序中,
然后向其传递消息,让其以某种方式处理这些消息,然后等待它为我们传回
结果。通过这种简单的消息传递机制,我们可以安全地在扩展程序的工作流中包含 eval
驱动的代码。
创建和使用沙盒
如果您希望直接查看代码,请下载沙盒示例扩展程序 关闭。这是一个基于 Handlebars 构建的小型消息传递 API 的有效示例 模板库,它应该会为您提供开始操作所需的一切。对于希望了解更多说明的用户,我们来一起详细了解一下该示例。
列出清单中的文件
应该在沙盒中运行的每个文件都必须在扩展程序清单中列出,方法是添加
sandbox
属性。这是非常重要的一步,而且很容易被忘记,因此请仔细检查
您的沙盒文件会在清单中列出在此示例中,我们将对名为“sandbox.html”的文件进行沙盒化处理。清单条目如下所示:
{
...,
"sandbox": {
"pages": ["sandbox.html"]
},
...
}
加载沙盒文件
为了对沙盒化文件执行一些有趣的操作,我们需要在
可以通过该扩展程序的代码进行处理在这里,sandbox.html 已通过 iframe
加载到扩展程序页面中。网页的 JavaScript 文件包含以下代码:每当用户点击浏览器操作时,该代码都会通过查找网页上的 iframe
并对其 contentWindow
调用 postMessage()
,将消息发送到沙盒。消息是一个包含以下三个属性的对象: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
插入它不会构成重大安全风险,因为我们信任在沙盒中呈现的内容。
这种机制使模板化变得简单,但它当然不仅限于模板化。在严格的内容安全政策下,任何无法直接运行的代码都可以放入沙盒中;事实上,将扩展程序中会正确运行的组件放入沙盒中通常很有用,这样可以将程序的每个部分限制为仅拥有执行所需的最少特权集。2012 年 Google I/O 大会上的 Writing Secure Web Apps and Chrome Extensions 演示提供了一些很好的示例,展示了这些技术的实际运用,值得您花 56 分钟的时间观看。