Skip to content
This repository was archived by the owner on Jul 29, 2022. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
169 changes: 169 additions & 0 deletions packages/electron-app/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,172 @@ This command assumes that we've built a CRA app inside `packages/light-apps`. It
### `yarn start`

Runs `electron-webpack dev`, which tells Electron to load http://localhost:3000, which is most likely the port on which CRA's dev mode runs. It reloads Electron on any code change inside the electron codebase.

## Electron Security Checklist:
https://electronjs.org/docs/tutorial/security#1-only-load-secure-content

Inspired by: https://github.com/paritytech/fether/pull/451

#### Cheatsheet
1. Only Load Secure Content (https/wss/ftps over http/ws/ftp)
2. Disable Node.js Integration for Remote Content
* Prevents XSS attacks from escalating to a Remote Code Execution Attack - limits the attack surface to the renderer process.
* **Node Integration + XSS in renderer process = RCE**
<pre>
const mainWindow = new BrowserWindow({
webPreferences: {
nodeIntegration: false,
nodeIntegrationInWorker: false,
preload: path.join(app.getAppPath(), 'preload.js')
}
});
</pre>
* Good: instead of allowing full access to the Node API (including stuff like fs, io, etc.), only export necessary features via a preload script, making sure to validate arguments properly.

**N.B. SLUI: preload not necessary, as there's no need for main<->renderer communication (yet)**
3. Context Isolation - separates Javascript contexts between preload scripts and the page's scripts, and between Electon's internal scripts and the page's scripts, preventing so-called "Prototype Pollution Attacks", i.e. extra protection against RCE's.
* `contextIsolation` is false by default.
* this means by default, you have shared context between the preload scripts and the page's scripts, i.e. `window.variable` set in `preload.js` is defined in the `BrowserWindow`'s context.

**N.B. SLUI: again not a concern considering we don't need preload scripts at all, but good to keep in mind.**
4. Handle Session Permission Requests from Remote Content
* The default setting in Electron is to approve all permission requests unless the developer explicitly tells it to handle it a particular way.
**N.B. SLUI: only some permissions matter in our context.**
Required Permissions:
```
contentSettings
debugger: only Dev.
desktopCapture: only Dev.
fileBrowserHandler: This is needed for JSON File Recovery.
idle: It could be a useful security measure in case user is AFK
notifications: Yes - extend from Alerts.
```

* Electron implements the same types of permissions as Chromium as listed below:
```
activeTab: we don't use tabs
alarms: not needed
background: not needed
bookmarks: not needed
browsingData: not needed
certificateProvider: yes
clipboardRead: no
clipboardWrite: no
contentSettings: only allow https, ftps, wss
contextMenus: no
cookies: no
debugger: not needed in production, only in development environment.
declarativeContent
declarativeNetRequest: cannot see a need to block or redirect network requests ATOW.
declarativeWebRequest: cannot see a need to block or redirect network requests ATOW.
desktopCapture: disable in Production, enable in Development.
displaySource: no.
dns: no.
documentScan: no
downloads: no.
enterprise.deviceAttributes: no.
enterprise.hardwarePlatform: no.
enterprise.platformKeys: no.
experimental: no.
fileBrowserHandler: This is needed for JSON File Recovery.
fileSystemProvider: no need
fontSettings: no need
gcm: no need
geolocation: definitely no need for this.
history: no need | using react router
identity: no need
idle: It could be a useful security measure in case user is AFK
management: no need
nativeMessaging: no need
networking.config: no need
notifications: Yes.
pageCapture: no need
platformKeys: no need.
power: no need.
printerProvider: no need.
privacy: no need.
processes: no.
proxy: no.
sessions: no.
signedInDevices: no.
storage: no.
system.cpu: no
system.display: no
system.memory: no
system.storage: no
tabCapture: no.
tabs: no need
topSites: no
tts: nice for accessibility in the future but honestly probably not necessary.
ttsEngine: nice for accessibility in the future but honestly probably not necessary.
unlimitedStorage
vpnProvider: no
wallpaper: not needed
webNavigation: no
webRequest: no
webRequestBlocking: no
```
5. Do Not Disable WebSecurity (default) - self explanatory
6. Define a Content Security Policy - not relevant (yet) as we're not loading any external web pages
7. Do Not Set allowRunningInsecureContent to true (default) - self explanatory
8. Do Not Enable Experimental Features (default) - self explanatory
9. Do Not Use enableBlinkFeatures (default) - self explanatory
10. Do Not Use allowpopups (default) - self explanatory
11. Verify WebView Options Before Creation:
* A WebView created in the renderer process will always create its own renderer process using its own `webPreferences`.
```
app.on('web-contents-created', (event, contents) => {
contents.on('will-attach-webview', (event, webPreferences, params) => {
// Strip away preload scripts if unused or verify their location is legitimate
delete webPreferences.preload
delete webPreferences.preloadURL

// Disable Node.js integration
webPreferences.nodeIntegration = false

// Verify URL being loaded
if (!params.src.startsWith('https://example.com/')) {
event.preventDefault()
}
})
})
```
**N.B. SLUI: we don't load any webpages, and if we do in the future it'll be in a browser so this is not a concern.**
12. Disable or limit navigation
```
const URL = require('url').URL

app.on('web-contents-created', (event, contents) => {
contents.on('will-navigate', (event, navigationUrl) => {
const parsedUrl = new URL(navigationUrl)

if (parsedUrl.origin !== 'https://example.com') {
event.preventDefault()
}
})
})
```
**N.B. SLUI: While not used yet, we may in the future want to navigate to the api docs page, or the forum, or other substrate related sites.**

13. Disable or limit creation of new windows
```
const { shell } = require('electron')

app.on('web-contents-created', (event, contents) => {
contents.on('new-window', (event, navigationUrl) => {
// if somehow there is a new-window event, block it.
event.preventDefault()
})
})
```
**N.B. SLUI: we just need the one BrowserWindow**
14. Do not use openExternal with untrusted content
15. Disable the remote module: the `remote` module would allow the renderer process to access APIs normally only available in the main process.
```
const mainWindow = new BrowserWindow({
webPreferences: {
enableRemoteModule: false
}
})
```
16. Filter the remote module: relevant only if we cannot disable remote module.
1 change: 1 addition & 0 deletions packages/electron-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"dependencies": {
"@types/electron-devtools-installer": "^2.2.0",
"electron-devtools-installer": "^2.2.4",
"pino": "^5.12.3",
"source-map-support": "^0.5.10"
}
}
53 changes: 50 additions & 3 deletions packages/electron-app/src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,42 @@

import electron from 'electron';
import installExtension, { REACT_DEVELOPER_TOOLS } from 'electron-devtools-installer';
import Pino from 'pino';
import path from 'path';
import url from 'url';

import { staticPath } from './util/staticPath';
import { CSP, staticPath } from './util';

const { app, BrowserWindow } = electron;
const { app, BrowserWindow, session } = electron;
const pino = Pino();
let mainWindow: Electron.BrowserWindow | undefined;

// https://electronjs.org/docs/tutorial/security#electron-security-warnings
process.env.ELECTRON_ENABLE_SECURITY_WARNINGS = 'true';

function createWindow () {
mainWindow = new BrowserWindow({
height: 1920,
resizable: true,
width: 1440
width: 1440,
webPreferences: {
allowRunningInsecureContent: false,
contextIsolation: true, // prevent context sharing between renderer<->main processes
devTools: true,
enableBlinkFeatures: '', // https://electronjs.org/docs/tutorial/security#9-do-not-use-enableblinkfeatures
enableRemoteModule: false,
experimentalFeatures: false, // Do not set to true
navigateOnDragDrop: false,
nativeWindowOpen: true,
nodeIntegration: false, // https://electronjs.org/docs/tutorial/security#2-disable-nodejs-integration-for-remote-content
nodeIntegrationInWorker: false, // https://electronjs.org/docs/tutorial/security#2-disable-nodejs-integration-for-remote-content
plugins: false,
sandbox: true, // isolate all BrowserWindow instance environments
safeDialogs: true,
safeDialogsMessage: 'Electron consecutive dialog protection was triggered',
webSecurity: true, // https://electronjs.org/docs/tutorial/security#5-do-not-disable-websecurity
webviewTag: false // Associated with `will-attach-webview`
}
});

mainWindow.loadURL(
Expand All @@ -34,6 +57,23 @@ function createWindow () {
.catch((err: string) => console.log('An error occurred: ', err));
}

// Content Security Policy (CSP)
session.defaultSession!.webRequest.onHeadersReceived((details, callback) => {
// Note: `onHeadersReceived` will not be called in prod, because we use the
// file:// protocol: https://electronjs.org/docs/tutorial/security#csp-meta-tag
// Instead, the CSP are the ones in the meta tag inside index.html
pino.debug(
'Configuring Content-Security-Policy for environment development'
);

callback({
responseHeaders: {
...details.responseHeaders,
'Content-Security-Policy': [CSP]
}
});
});

mainWindow.on('closed', () => {
mainWindow = undefined;
});
Expand All @@ -49,6 +89,13 @@ app.on('activate', function () {
}
});

// Uncomment this block if ever we decide to use a webview in the app.
// app.on('web-contents-created', (event, contents) => {
// contents.on('will-attach-webview', (event, webPreferences, params) => {
// event.preventDefault();
// });
// });

// Quit when all windows are closed.
app.on('window-all-closed', function () {
// On OS X it is common for applications and their menu bar
Expand Down
5 changes: 5 additions & 0 deletions packages/electron-app/src/main/util/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Copyright 2018-2019 @paritytech/substrate-light-ui authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.

export const IS_PROD = process.env.NODE_ENV === 'production';
47 changes: 47 additions & 0 deletions packages/electron-app/src/main/util/csp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright 2018-2019 @paritytech/substrate-light-ui authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.

import { IS_PROD } from './constants';

// References:
// * https://github.com/parity-js/shell
// * https://github.com/paritytech/fether
const CSP_CONFIG = {
// Disallow mixed content
blockAllMixedContent: 'block-all-mixed-content;',
// Disallow framing and web workers.
childSrc: "child-src 'none';",
// FIXME - Only allow connecting to WSS and HTTPS servers.
connectSrc: IS_PROD
? 'connect-src ws:;'
// Also allow http in dev mode, for CRA
: 'connect-src http: ws:;',
// Fallback for missing directives.
// Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/default-src
//
// Disallow everything as fallback by default for all CSP fetch directives.
defaultSrc: "default-src 'none';",
// Disallow fonts, we allow https because we are loading from Google Fonts (FIXME don't load from google)
fontSrc: "font-src 'self' data: https:;",
// Disallow submitting any forms
formAction: "form-action 'none';",
// Disallow framing.
frameSrc: "frame-src 'none';",
// Restrict images to only images from known sources
imgSrc: "img-src 'self' data:;",
// Disallow manifests.
manifestSrc: "manifest-src 'none';",
// Disallow media.
mediaSrc: "media-src 'none';",
// Disallow fonts and `<webview>` objects
objectSrc: "object-src 'none';",
// Disallow unknown scripts
scriptSrc: "script-src 'self' 'unsafe-inline';",
// Disallow stylesheets, we allow https because we are loading from Google Fonts (FIXME don't load from google)
styleSrc: "style-src 'self' 'unsafe-inline' https:;",
// Disallow workers, allow `blob:` for camera access if needed
workerSrc: "worker-src 'none';"
};

export const CSP = Object.values(CSP_CONFIG).join(' ');
1 change: 1 addition & 0 deletions packages/electron-app/src/main/util/declarations/pino.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
declare module 'pino';
7 changes: 7 additions & 0 deletions packages/electron-app/src/main/util/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// Copyright 2018-2019 @paritytech/substrate-light-ui authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.

export * from './constants';
export * from './csp';
export * from './staticPath';
Loading