Определить заголовок HTTP CSP в приложении Electron

Следуя документации API, я не понимаю, как определить HTTP-заголовок Content-Security-Policy для рендеринга моего приложения Electron. Я всегда получаю предупреждение в DevTools.

Я старался:

1) Скопируйте/Вставьте код в API Doc, слепо:

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 http-equiv="Content-Security-Policy" content="default-src 'self'>

Ответы

Ответ 1

Не знаете, почему в документации содержится этот сломанный код. Это сбило меня с ума, но я нашел рабочее решение путем проб и ошибок:

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

Поэтому аргумент заголовков должен быть объектом с той же структурой, что и исходные заголовки, полученные в details.responseHeaders. И исходные заголовки также должны быть включены в переданный объект, потому что этот объект, по-видимому, полностью заменяет исходные заголовки ответов.

Опция extraHeaders не предназначена для заголовков ответов. Это для заголовков запросов, отправленных на сервер.

Ответ 2

Если вы хотите использовать CSP как в режиме разработки (с ресурсами, загружаемыми по протоколу http://), так и в режиме prod (file:// protocol), то как вы можете это сделать:

Сначала удалите метаданные Content-Security-Policy из src/index.html - нам нужно внедрить ее только для режима prod, потому что

  • onHeadersReceived не будет работать для протокола file://, как подтверждают документы Electron, а также потому, что
  • если мы сохраним его в src/index.html для режима Dev, он переопределит onHeadersReceived хотя бы для части ресурсов, а для режима Dev нам потребуются другие настройки.

Затем мы можем добавить его в режим Prod с помощью gulp-inject:

// 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.

А для режима dev давайте использовать onHeadersReceived, как в примере с документами Electron:

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.

Ответ 3

Как указано в электронных документах, вам придется использовать метатег Content Security Policy (CSP) в html файле при загрузке renderer.html по схеме file:// (IIRC вы делаете это в приведенный выше пример).

Если вы хотите условно настроить политику безопасности контента для среды prod и dev, вы можете динамически сгенерировать эту строку внутри html на этапе сборки. Я предлагаю использовать шаблонизатор, например, mustache.js (используется в примере).

Пример (файловые ресурсы)

В моем случае я хотел включить горячую замену модуля (HMR) через веб-сокеты и ресурс file:// в режиме разработки, что требовало ослабления правил 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 (для prod можно использовать значения по умолчанию):

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

Ресурсы URL

Для использования с ресурсами URL, вы можете придерживаться этого примера:

const { session } = require('electron')

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

Убедитесь, что ваши пользовательские заголовки ответов CSP объединены с заголовками по умолчанию - вы не сделаете этого в приведенном выше примере. Здесь вы также можете проверить условия для окружающей среды.

Надеюсь, это поможет.

Ответ 4

В вашем вопросе недостаточно подробностей, чтобы знать, есть ли у вас проблемы с начальной загрузкой или последующими веб-запросами, но моя проблема была связана с начальной загрузкой файла. С приложением Electron, использующим React, я получал предупреждения об использовании встроенных сценариев даже с кодом kayahr. Это связано с тем, что метод onHeadersReceived перехватывает только те запросы, которые были сделаны после первоначальной загрузки приложения. Это не остановит никаких предупреждений от начальной загрузки приложения.

В итоге мне пришлось использовать шаблоны во время сборки приложения, чтобы добавить одноразовый номер во встроенный скрипт и стиль, а также в заголовок CSP в файле HTML, который приложение загружает изначально.

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, если кому-то интересно, хотя есть еще одно предупреждение при запуске приложения, потому что один из пакетов, которые я использую, тянет в response-beautiful-dnd, который добавляет встроенные стили, но в настоящее время не принимает одноразовые.

Ответ 5

Установите следующий метатег в средствах визуализации.

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

Пожалуйста, ознакомьтесь с моим репозиторием на github electronic-renderer-CSP-sample, содержащим образцы как для nonce, так и для Методы SHA для внутреннего & внешние js файлы.