chrome插件之内容安全策略🚫

一、前面的话

今天我们来认识一个chrome插件中的核心概念,它就是chrome插件安全系统的重要组成部分 ------ Content Security Policy (CSP)。中文可以译作内容安全策略

换句话说,chrome插件要保证自己的运行的内容是安全的,不会导致用户的利益和隐私受到恶意的读取。所以chrome插件的设计者为chrome制定的一些限制🚫,这个就是内容安全策略的通俗理解。

为了更好的帮你全面的认识内容安全策略,接下来我们会从下面几个方面来介绍:

  • 表现在哪些方面?
  • 为什么要有这个?
  • 什么是sanbox?

可以使用eval么?

大家都知道chrome插件开发是完全拥抱web技术的,也就是说我们可以使用js、css、html来开发插件就完全足够了,那么在js中,有一个函数叫做eval,它相当于一个javascript执行入口,可以将一段字符串的代码交给js引擎去执行。

js 复制代码
console.log(eval('2 + 2'));
// 输出: 4

console.log(eval(new String('2 + 2')));
// 输出: 2 + 2

console.log(eval('2 + 2') === eval('4'));
// 输出: true

console.log(eval('2 + 2') === eval(new String('2 + 2')));
// 输出: false

以上在web端运行时完全没问题的,但是如果在chrome插件中使用eval就会有问题,它会报下面这个错:

根据这个报错,我们就回答了第一个问题,内容安全策略的第一个表现就是禁止在chrome上下文执行环境中使用eval

如果您读过我的插件系列的文章,您就会知道,除了不能使用eval之外,在pupup.html、option.html中都不能够使用内联的scirpt脚本,像下面这样就是不允许的:

html 复制代码
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>chrome page</title>
  </head>
  <body>
    <h1>我是popup.html</h1>
    <script>
      console.log("这是popup.html的内联脚本");
    </script>
  </body>
</html>

必须通过使用一个pupop.js文件引入的方式来注入脚本,这些其实都是内容安全策略的限制。

为什么禁止eval?

其实禁止eval的理由很简单------因为它太强大了,eval可以运行任何一段脚本,那就意味着用户可能会写下类似下面这样的代码:

js 复制代码
const script = `
  chrome.cookies.get({ 
    url: tabUrl, 
    name: "cookieName" 
  });
`

eval(script)

如果是插件作者自己因为业务需求需要获取cookie不要紧,但是要知道这个script可能来自网络,如果网络中有恶意的脚本交给chrome运行了,就会可能导致chromeAPI的滥用,导致用户的隐私信息比如cookie,书签等信息的泄露,这对于用户来说显然是一种损失。

所以禁止eval的原因之一就是因为害怕恶意的脚本借助强大的chromeAPI的能力在插件上下文运行了,从而非法获取用户的隐私信息。

除了禁用eval,还有其他的一些特性,比如你也不能使用new Function()的方式来声明一个函数;

在popup.html、options.html等插件页面中不能有内联的脚本等等,他们都是内容安全策略的限制。

什么是sandbox?

那么如果我们就是想要使用eval怎么办呢?其实也是有办法的,我们只需要找到根源就好:

内容安全策略之所以限制部分部分Javascript特性的使用,就是因为它们可能会导致chromeAPI的滥用,因此只需要让他们在一个没有办法调用chromeAPI的环境中执行就好了,这个环境就是sandbox。

sandbox通常指的是沙箱环境,它是一种安全机制,用于隔离插件的代码和操作,以防止恶意行为或不当访问用户的敏感数据。它有以下几个作用:

  1. 代码隔离: 沙箱环境将插件的代码与浏览器的核心代码和其他插件隔离开来。这意味着插件的JavaScript代码无法直接访问浏览器的敏感信息或其他插件的数据,从而增强了安全性。
  2. 限制执行环境: 沙箱通常会限制插件的代码执行环境,防止插件滥用底层系统资源或执行危险操作。这包括限制文件系统访问、网络请求和系统级操作等。
  3. 权限控制: Chrome插件系统通过权限模型来控制插件的功能。插件需要在其manifest.json文件中声明所需的权限,用户在安装插件时需要授予这些权限。这有助于确保插件只能执行其明确定义的任务。
  4. 内容脚本沙箱: 插件中的内容脚本(Content Scripts)通常在Web页面上运行,它们也受到沙箱机制的保护。这意味着内容脚本的代码只能访问所嵌入页面的内容,而无法直接访问插件的API或其他插件资源。
  5. 沙箱沙盒化: 沙箱环境本身也可以进一步沙盒化,以确保插件中的不同组件之间相互隔离。这意味着插件的不同部分(如背景脚本、内容脚本和弹出窗口)之间通常不能直接共享变量或数据。

二、沙盒解决方案

接下来我们将采用在沙箱环境下运行一段脚本,首先需要在manifest.json中声明一下:

json 复制代码
{
  ...,
  "sandbox": {
     "pages": ["sandbox.html"] // 指定需要沙盒化的page
  },
  ...
}

我们在sandbox.html中测试一下一些特定的特性是否可以使用:

html 复制代码
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <h1>sanbox demo</h1>

    <script>
      console.log("这是来自sandbox的chrome", chrome);
      eval('console.log("sandbox")');
    </script>
  </body>
</html>

然后在background.js中设置打开这个sandbox.html

js 复制代码
chrome.runtime.onInstalled.addListener(() => {
  console.log("这是来自background的chrome", chrome);

  chrome.tabs.create({
    url: "sandbox.html",
  });
});

当我们加载插件的时候,就会打开sandbox.html,结果如下:

    1. eval()能够正常执行
    1. 能够使用内联脚本
    1. 不同于background环境中的chrome对象,sandbox环境中的chrome只有极少的属性,也就是说在sandbox环境中,无法调用chromeAPI中类似storage、tabs等高级特性。

所以结论是:我们其实可以在插件中使用eval等在web端常用的特性,只需要将它运行的环境在manifest.json中显示的指定为沙盒环境就好了。

可能有的同学就会问了,虽然解决了使用eval的问题,但是又产生了一个新的问题,那就是既然没办法使用chrome的高级特性,那通信咋办呀,由沙盒环境产出的结果怎么通知给外部呀!

这个其实就要用到我们之前的一个知识了 ------ postMessage,它的使用文档在这里,这个API允许你向同源的所有域发送消息,也可以监听来自同源的域发送的消息。在插件中,我们改动一下上面的案例,使用类似下面的方式:

第一步:在background.js创建一个demo.html,它属于插件的page。

js 复制代码
// background.js
chrome.runtime.onInstalled.addListener(() => {
  console.log("这是来自background的chrome", chrome);

  chrome.tabs.create({
    url: "demo.html",
  });
});

第二步:在demo中嵌入sandbox.html

html 复制代码
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <h1>我是parent</h1>
    <button id="sendMessage">向sandbox发送消息</button>

    <script src="./demo.js"></script>
    <iframe
      id="theFrame"
      src="sandbox.html"
      frameborder="0"
      width="500"
      height="300"
    ></iframe>
  </body>
</html>
js 复制代码
// demo.js 通过给iframe发送消息
document.getElementById("sendMessage").addEventListener("click", () => {
  document
    .getElementById("theFrame")
    .contentWindow.postMessage("hello sandbox", "*");
});

第三步:在sandbox.html中做监听

html 复制代码
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <h1>我是sandbox</h1>
    <p>我是来自parent的data: <span id="show"></span></p>
    <script>
      eval('console.log("我可以执行eval")');

      window.addEventListener("message", (event) => {
        const receivedMessage = event.data;
        const show = document.getElementById("show");
        show.innerHTML = receivedMessage;
      });
    </script>
  </body>
</html>

效果如下:

至此,我们成功实现了沙盒环境的创建和与插件page进行通信。

三、定制策略

通过上面的学习,我们知道在chrome插件的世界里,有两种page:

  • 插件page,例如:popup.htmloption.html 、通过chrome.tabs.create() 创建出来的page等...
  • 沙盒page,通过在manifest.json中的sandbox指定的page。

它们都不是完美的,正如这个世界上没有完美的人一样。

插件Page不是完美的,因为它无法调用"eval",沙盒page不是完美的,因为它无法调用chromeAPI。他们都不完美,但是他们合作起来(通信),可以做到很多事情。

为什么出现上述的现象,就是因为内容安全策略的限制🚫,实际上在chrome插件的世界里,内容安全策略也有一种描述,默认情况下它在manifest.json中是这样的:

json 复制代码
{
  // ...
  "content_security_policy": {
    "extension_pages": "script-src 'self'; object-src 'self';",
    "sandbox": "sandbox allow-scripts allow-forms allow-popups allow-modals; script-src 'self' 'unsafe-inline' 'unsafe-eval'; child-src 'self';"
  }
  // ...
}

"extension_pages"的属性值,便是插件page的限制内容。同理,"sandbox"的属性值就是沙盒page的限制内容。

我们当然可以去修改这个策略,但是大部分情况下是不需要的,因为对于"extension_pages"来说,默认策略已经是最宽松的限制了,你不能比它更宽松,你只能更严格的限制它。如果你取消某些规则,例如:把"script-src 'self'"解除掉,就会报错:

所以我的建议是,不要去修改这个策略了,因为完全没有必要,因为它已经是最宽松的策略了,如果您不希望自己的人生更加坎坷,不要去轻易修改它。

四、资源

上面的案例我放在github仓库啦:地址在这里

插件系列文章:

另外我有一个自己的网站,欢迎来看看 new-story.cn

创作不易,如果您觉得文章有任何帮助到您的地方,或者触碰到了自己的知识盲区,请帮我点赞收藏一下,或者关注我,我会产出更多高质量文章,最后感谢您的阅读,祝愿大家越来越好。

相关推荐
前端大卫2 小时前
Vue3 + Element-Plus 自定义虚拟表格滚动实现方案【附源码】
前端
却尘3 小时前
Next.js 请求最佳实践 - vercel 2026一月发布指南
前端·react.js·next.js
ccnocare3 小时前
浅浅看一下设计模式
前端
Lee川3 小时前
🎬 从标签到屏幕:揭秘现代网页构建与适配之道
前端·面试
Ticnix3 小时前
ECharts初始化、销毁、resize 适配组件封装(含完整封装代码)
前端·echarts
纯爱掌门人3 小时前
终焉轮回里,藏着 AI 与人类的答案
前端·人工智能·aigc
twl3 小时前
OpenClaw 深度技术解析
前端
崔庆才丨静觅3 小时前
比官方便宜一半以上!Grok API 申请及使用
前端
星光不问赶路人4 小时前
vue3使用jsx语法详解
前端·vue.js
天蓝色的鱼鱼4 小时前
shadcn/ui,给你一个真正可控的UI组件库
前端