在Electron App中定义CSP HTTP标头

时间:2018-08-22 14:47:44

标签: javascript electron content-security-policy

the API documentation之后,我不明白如何为Electron应用程序的呈现器定义Content-Security-Policy HTTP标头。我总是在DevTools中收到警告。

我尝试过:

1)盲目复制/粘贴API文档中的代码:

app.on('ready', () => {
    const {session} = require('electron')
    session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
        callback({responseHeaders: `default-src 'self'`})
    })

    win = new BrowserWindow(...)
    win.loadUrl(...)
}

(顺便说一句,我不明白为什么字符串中缺少“ Content-Security-Policy:”。但是添加它不会改变任何内容)

2)用相同的代码修改渲染器的会话:

win = new BrowserWindow(...)
win.loadUrl(...)

const ses = win.webContents.session;
ses.webRequest.onHeadersReceived((details, callback) => {
  callback({responseHeaders: `default-src 'self'`})
})

3)向该渲染器添加一个额外的标题:

win = new BrowserWindow(...)
win.loadURL(`file://${__dirname}/renderer.html`,{
    extraHeaders: `Content-Security-Policy: default-src 'self'`
});

...

唯一有效的方法是在渲染器HTML文件中使用meta标签:

<meta http-equiv="Content-Security-Policy" content="default-src 'self'>

5 个答案:

答案 0 :(得分:7)

不确定为什么文档中包含此损坏的代码。这让我很困惑,但是我通过反复试验找到了一个可行的解决方案:

session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
    callback({ responseHeaders: Object.assign({
        "Content-Security-Policy": [ "default-src 'self'" ]
    }, details.responseHeaders)});
});

因此headers参数必须是与details.responseHeaders中收到的原始标头具有相同结构的对象。并且原始标头也必须包含在传递的对象中,因为该对象似乎完全替代了原始响应标头。

extraHeaders选项不适用于响应头。用于发送到服务器的请求标头。

答案 1 :(得分:0)

如果您的目标是能够同时在开发模式(通过http://协议加载资源)和生产模式(file://协议)中使用CSP,则可以这样做:

首先,删除Content-Security-Policy从元src/index.html - 我们需要注入它仅用于PROD模式,因为

  • onHeadersReceived会为file://协议作为电子文档confirm,并且还因为
  • 不工作
  • 如果在开发模式下将其保留在src/index.html中,它将至少在部分资源上覆盖onHeadersReceived,对于开发模式,我们需要不同的设置。

然后我们可以使用gulp-inject将其注入Prod模式:

// in project dir
npm install --save-dev gulp-inject gulp
// src/index.html

<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <base href="">

  <meta name="viewport" content="width=device-width, initial-scale=1">
  <!-- inject:prod-headers -->
  <!-- src/prod-headers.html content will be injected here -->
  <!-- endinject -->
  <link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
  <app-root>Loading...</app-root>
</body>
</html>

// src/prod-headers.html
<meta http-equiv="Content-Security-Policy" content="default-src 'self'">
// gulpfile.js
var gulp = require('gulp');
var inject = require('gulp-inject');

gulp.task('insert-prod-headers', function () {
  return gulp.src('./dist/index.html')
    .pipe(inject(gulp.src('./src/prod-headers.html'), {
      starttag: '<!-- inject:prod-headers -->',
      transform: function (filePath, file) {
        // return file contents as string
        return file.contents.toString('utf8')
      }
    }))
    .pipe(gulp.dest('./dist'));
});

然后确保npx gulp insert-prod-headers在例如ng build生成dist/index.html

在开发模式下,我们使用与电子文档example类似的onHeadersReceived:

const args = process.argv.slice(1);
const devMode = args.some((val) => val === '--serve');
app.on('ready', () => {
    if (devMode) {
      const {session} = require('electron')
      session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
        callback({responseHeaders: `default-src http: ws:`})
      })
    }

    win = new BrowserWindow(...)
    win.loadUrl(...)
}

该溶液在Electron 4.0.3上进行了测试。

答案 2 :(得分:0)

Electron docs中所指出的那样,通过renderer.html方案(IIRC)加载file://时,您必须在html文件中使用内容安全策略(CSP)元标记。您可以在上面的示例中执行此操作。

如果要针对产品和开发环境有条件地调整内容安全策略,则可以在构建步骤中在html内部动态生成此字符串。我建议使用mustache.js之类的模板引擎(在示例中使用)。

示例(文件资源)

就我而言,我想在开发模式下通过websockets和file://资源启用热模块替换(HMR),这需要放宽CSP规则(但仅在dev中!)。

index.mustache:

<html>
  <head>
    <meta
      http-equiv="Content-Security-Policy"
      content="{{{cspContent}}}"
    />
  </head>
...

用于开发人员的cspContent.json:

{
  "cspContent": "default-src 'self'; connect-src 'self' ws:"
}

dev中的构建步骤(可以使用产品默认值):

npx mustache cspContent.json index.mustache > index.html

URL资源

要使用URL资源,您可以坚持使用this example

const { session } = require('electron')

session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
  callback({
    responseHeaders: {
      ...details.responseHeaders,
      'Content-Security-Policy': ['default-src \'none\'']
    }
  })
})

确保将自定义CSP响应标头与默认标头合并-在上面粘贴的示例中不要这样做。在这里,您还可以有条件地检查环境。

希望,它会有所帮助。

答案 3 :(得分:0)

您的问题中没有足够的细节来知道您是在初始加载还是后续的Web请求方面遇到问题,但是我的问题是初始文件加载。借助使用React的Electron应用程序,即使使用kayahr的代码,我也收到有关使用内联脚本的警告。这是因为onHeadersReceived方法仅捕获应用程序最初加载后发出的请求。它不会停止来自初始应用程序加载的任何警告。

我最终不得不在应用程序构建过程中使用模板,以将随机数添加到内联脚本和样式以及应用程序最初加载的HTML文件中的CSP标头中。

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'nonce-<%= scriptNonce %>'; style-src 'nonce-<%= styleNonce %>';">
    <link rel="stylesheet" type="text/css" href="./index.css" nonce=<%= styleNonce %>>
    <title>Basic Electron App</title>
  </head>
  <body>
    <div id="app"></div>
    <script type="application/javascript" nonce=<%= scriptNonce %>>
      require('./index.js');
    </script>
  </body>
</html>

index.css

body {
  margin: 0px;
}

.hello {
  font-family: "Century Gothic";
  width: 800px;
  margin: 70px auto;
  text-align: center;
}

并在gulfile.js中将以下内容添加到已经拥有的内容中,并确保此任务包含在管道中。您也可以只使用以下代码更新当前的html任务。

const template = require('gulp-template');
const uuidv4 = require('uuid/v4');

gulp.task('copy-html', () => {
  // Create nonces during the build and pass them to the template for use with inline scripts and styles
  const nonceData = {
    scriptNonce: new Buffer(uuidv4()).toString('base64'),
    styleNonce: new Buffer(uuidv4()).toString('base64')
  };
  return gulp.src('src/*.html')
  .pipe(template(nonceData))
  .pipe(gulp.dest('dist/'));
});

这是一个非常简单的示例。如果有人感兴趣,我会在https://github.com/NFabrizio/data-entry-electron-app上提供一个更完整的示例,尽管在运行该应用程序时仍会出现一条警告,因为我使用的其中一个包会拉入react-beautiful-dnd,这会添加内联样式,但不会当前接受随机数。

答案 4 :(得分:0)

在渲染器中设置以下元标记。

<meta http-equiv="Content-Security-Policy" content="script-src 'nonce-xxx or sha256-yyy' " />

请结帐我的github存储库electron-renderer-CSP-sample,其中还包含内部和外部js文件的nonce和SHA方法的示例。