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 12 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"
}
}
57 changes: 54 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, IS_PROD, 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)
// @ts-ignore session object will always be defined
session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
Comment thread
pmespresso marked this conversation as resolved.
Outdated
pino.debug(
`Configuring Content-Security-Policy for environment ${
IS_PROD ? 'production' : 'development'
}`
);

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

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

app.on('web-contents-created', (event, contents) => {
contents.on('will-attach-webview', (event, webPreferences, params) => {
Comment thread
pmespresso marked this conversation as resolved.
Outdated
// 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;
});
});

// 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';
59 changes: 59 additions & 0 deletions packages/electron-app/src/main/util/csp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// 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';

/* eslint-disable */
// 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.
// tslint:disable-next-line:quotemark
childSrc: "child-src 'none';",
// FIXME - Only allow connecting to WSS and HTTPS servers.
connectSrc: '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.
fontSrc: "font-src 'none';", // Additionally used in Parity-JS Shell `'self' data: https:`
Comment thread
pmespresso marked this conversation as resolved.
Outdated
// Disallow submitting any forms
formAction: "form-action 'none';",
// Disallow framing.
frameSrc: "frame-src 'none';",
imgSrc: !IS_PROD
? // Only allow HTTPS for images. Token provider logos must be https://
// Allow `data:` `blob:`.
"img-src 'self' 'unsafe-inline' file: data: blob: https:;"
: // Only allow HTTPS for images. Token provider logos must be https://
// Allow `data:` `blob:`.
"img-src 'unsafe-inline' file: data: blob: https:;", // Additionally used in Parity-JS Shell `'self'`
// Disallow manifests.
manifestSrc: "manifest-src 'none';",
// Disallow media.
mediaSrc: "media-src 'none';",
// Disallow fonts and `<webview>` objects
objectSrc: "object-src 'none';",
// Disallow prefetching.
prefetchSrc: "prefetch-src 'none';",
scriptSrc: !IS_PROD
? // Only allow `http:` and `unsafe-eval` in dev mode (required by create-react-app)
"script-src 'self' file: http: blob: 'unsafe-inline' 'unsafe-eval';"
: "script-src file: 'unsafe-inline';",
styleSrc: !IS_PROD
? "style-src 'self' 'unsafe-inline' file: blob:;" // Additionally used in Parity-JS Shell `data: https:`
: "style-src unsafe-inline' file: blob:;", // Additionally used in Parity-JS Shell `data: https:`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refused to load the stylesheet 'https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic&subset=latin' because it violates the following Content Security Policy directive: "style-src 'self' 'unsafe-inline' file: blob:".

Add https in both. Actually tbh I don't think there's need to differentiate between IS_PROD or not here.

// Allow `blob:` for camera access (worker)
workerSrc: 'worker-src blob:;' // Additionally used in Parity-JS Shell `'self' https:`
};
/* eslint-enable */

const CSP = Object.values(CSP_CONFIG).join(' ');

export { CSP };
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';
9 changes: 0 additions & 9 deletions packages/light-apps/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,6 @@
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.

You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.

To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>
4 changes: 2 additions & 2 deletions packages/light-apps/public/manifest.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"short_name": "SLUI",
"name": "Substrate Light UI",
"icons": [
{
"src": "favicon.ico",
Expand Down
Loading