Skip to content
This repository has been archived by the owner on Nov 10, 2023. It is now read-only.

Commit

Permalink
Electron config security improvements (#3702)
Browse files Browse the repository at this point in the history
* Disable experimentalFeatures and remoteModule

Use shell.openExternal only when needed

* Remove isDesktop from preload script
  • Loading branch information
Daniel Sanchez authored Mar 23, 2022
1 parent b3841a5 commit a15bac4
Show file tree
Hide file tree
Showing 8 changed files with 220 additions and 93 deletions.
10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"compile-electron": "tsc --project tsconfig.electron.json",
"eject": "rescripts eject",
"electron-build": "yarn compile-electron && electron-builder --mac",
"electron-dev": "yarn compile-electron && concurrently \"cross-env BROWSER=none REACT_APP_BUILD_FOR_DESKTOP=true yarn start\" \"wait-on http://localhost:3000 && electron .\"",
"electron-dev": "yarn compile-electron && concurrently \"cross-env BROWSER=none REACT_APP_BUILD_FOR_DESKTOP=true yarn start\" \"wait-on http://localhost:3000 && ELECTRON_ENV=development electron .\"",
"format:staged": "lint-staged",
"generate-types": "yarn generate-types:spendingLimit && yarn generate-types:safeDeployments && yarn generate-types:erc20 && yarn generate-types:erc721",
"generate-types:erc20": "cross-env typechain --target=web3-v1 --out-dir './src/types/contracts' './node_modules/@openzeppelin/contracts/build/contracts/ERC20.json'",
Expand Down Expand Up @@ -194,7 +194,7 @@
"ethereumjs-abi": "0.6.8",
"ethereumjs-util": "^7.0.10",
"exponential-backoff": "^3.1.0",
"express": "^4.17.2",
"express": "^4.17.3",
"final-form": "^4.20.2",
"final-form-calculate": "^1.3.2",
"framer-motion": "^4.1.17",
Expand Down Expand Up @@ -244,7 +244,7 @@
"@testing-library/react": "^12.1.2",
"@testing-library/react-hooks": "^7.0.2",
"@typechain/web3-v1": "^3.0.0",
"@types/detect-port": "^1.3.1",
"@types/detect-port": "^1.3.2",
"@types/express": "^4.17.13",
"@types/history": "4.6.2",
"@types/jest": "^27.0.1",
Expand All @@ -263,8 +263,8 @@
"cross-env": "^7.0.3",
"dotenv": "^10.0.0",
"dotenv-expand": "^5.1.0",
"electron": "13.5.2",
"electron-builder": "22.14.5",
"electron": "13.6.9",
"electron-builder": "22.14.13",
"electron-notarize": "1.1.1",
"eslint": "^7.32.0",
"eslint-config-prettier": "^8.3.0",
Expand Down
65 changes: 37 additions & 28 deletions public/electron.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,34 @@
import express from 'express'
import detect from 'detect-port'
import { app, BrowserWindow, Menu, nativeImage, session, shell, screen } from 'electron'
import log from 'electron-log'
import { autoUpdater } from 'electron-updater'
import express from 'express'
import fs from 'fs'
import electron from 'electron'
import https from 'https'
import { autoUpdater } from 'electron-updater'
import detect from 'detect-port'
import path from 'path'

const { app, session, BrowserWindow, shell, Menu } = electron
const isDev = !app.isPackaged
const DEFAULT_PORT = 5000
app.allowRendererProcessReuse = false
const trezorRegExp = new RegExp(`/https:\/\/((.+\.)*trezor\.io)/gi`)
const portisRegExp = new RegExp(`/https:\/\/((.+\.)*portis\.io)/gi`)

const isDev = process.env.ELECTRON_ENV === 'development'
const options = {
key: fs.readFileSync(path.join(__dirname, './ssl/server.key')),
cert: fs.readFileSync(path.join(__dirname, './ssl/server.crt')),
ca: fs.readFileSync(path.join(__dirname, './ssl/rootCA.crt')),
}

async function getFreePort(): Promise<number> {
let mainWindow

const getFreePort = async (): Promise<number> => {
const port = await detect(DEFAULT_PORT)

return port
}

function createServer(port: number): void {
const createServer = (port: number): void => {
const app = express()
app.disable('x-powered-by')
const staticRoute = path.join(__dirname, '../build')

// We define same route as in package.json -> homepage
Expand All @@ -33,12 +37,8 @@ function createServer(port: number): void {
https.createServer(options, app).listen(port, '127.0.0.1')
}

let mainWindow

function getOpenedWindow(url: string, options) {
const display = electron.screen.getPrimaryDisplay()
const width = display.bounds.width
const height = display.bounds.height
const getOpenedWindow = (url: string, options) => {
const { width, height } = screen.getPrimaryDisplay().bounds

// filter all requests to trezor-bridge and change origin to make it work
const filter = {
Expand All @@ -47,14 +47,14 @@ function getOpenedWindow(url: string, options) {

options.webPreferences.affinity = 'main-window'

if (url.includes('trezor')) {
if (trezorRegExp.test(url)) {
session.defaultSession.webRequest.onBeforeSendHeaders(filter, (details, callback) => {
details.requestHeaders['Origin'] = 'https://connect.trezor.io'
callback({ cancel: false, requestHeaders: details.requestHeaders })
})
}

if (url.includes('wallet.portis') || url.includes('trezor') || url.includes('app.tor.us')) {
if (portisRegExp.test(url) || trezorRegExp.test(url) || url.includes('app.tor.us')) {
const win = new BrowserWindow({
width: 350,
height: 700,
Expand All @@ -64,33 +64,39 @@ function getOpenedWindow(url: string, options) {
fullscreen: false,
show: false,
})
win.webContents.on('new-window', function (event, url) {
if (url.includes('trezor') && url.includes('bridge')) shell.openExternal(url)

win.webContents.on('new-window', (event, url) => {
if (trezorRegExp.test(url) && url.includes('bridge')) {
shell.openExternal(url)
}
})

win.once('ready-to-show', () => win.show())

if (!options.webPreferences) {
win.loadURL(url)
}

return win
}

return null
}

function createWindow(port = DEFAULT_PORT) {
const createWindow = (port = DEFAULT_PORT) => {
mainWindow = new BrowserWindow({
show: false,
width: 1366,
height: 768,
webPreferences: {
preload: path.join(__dirname, '../scripts/preload.js'),
experimentalFeatures: true,
enableRemoteModule: true,
// experimentalFeatures not needed now unless migrating to WebHID Electron >= 16
// experimentalFeatures: true,
// Needed to load Ledger from preload scripts, sharing context with main window
contextIsolation: false,
nativeWindowOpen: true, // need to be set in order to display modal
nativeWindowOpen: true, // need to be set in order to display modal. Not needed for Electron >= 15
},
icon: electron.nativeImage.createFromPath(path.join(__dirname, '../build/resources/safe.png')),
icon: nativeImage.createFromPath(path.join(__dirname, '../build/resources/safe.png')),
})

mainWindow.once('ready-to-show', () => {
Expand All @@ -110,7 +116,7 @@ function createWindow(port = DEFAULT_PORT) {
mainWindow.setMenu(null)
mainWindow.setMenuBarVisibility(false)

mainWindow.webContents.on('new-window', function (event, url, frameName, disposition, options) {
mainWindow.webContents.on('new-window', (event, url, frameName, disposition, options) => {
const win = getOpenedWindow(url, options)
if (win) {
win.once('ready-to-show', () => win.show())
Expand All @@ -120,7 +126,8 @@ function createWindow(port = DEFAULT_PORT) {
}

event.newGuest = win
} else shell.openExternal(url)
}
// else shell.openExternal(url)
})

mainWindow.webContents.on('did-finish-load', () => {
Expand All @@ -135,12 +142,14 @@ function createWindow(port = DEFAULT_PORT) {
mainWindow.on('closed', () => (mainWindow = null))
}

process.on('uncaughtException', function (error) {
process.on('uncaughtException', (error) => {
log.error(error)
})

app.allowRendererProcessReuse = false

app.userAgentFallback =
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) old-airport-include/1.0.0 Chrome Electron/13.5.2 Safari/537.36'
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) old-airport-include/1.0.0 Chrome Electron/13.6.9 Safari/537.36'

app.whenReady().then(async () => {
// Hide the menu
Expand Down
9 changes: 1 addition & 8 deletions scripts/preload.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,7 @@
// All of the Node.js APIs are available in the preload process.
// It has the same sandbox as a Chrome extension.
// https://stackoverflow.com/a/58164407/7820085
const {
remote: { app },
} = require('electron')
const log = require('electron-log')
const TransportNodeHid = require('@ledgerhq/hw-transport-node-hid-singleton').default

const isDev = !app.isPackaged
global.isDesktop = true
const isDev = process.env.ELECTRON_ENV === 'development'
global.TransportNodeHid = TransportNodeHid

window.addEventListener('DOMContentLoaded', () => {
Expand Down
13 changes: 6 additions & 7 deletions src/components/CookiesBanner/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ import AlertRedIcon from './assets/alert-red.svg'
import IntercomIcon from './assets/intercom.png'
import { useSafeAppUrl } from 'src/logic/hooks/useSafeAppUrl'
import { CookieAttributes } from 'js-cookie'

const isDesktop = process.env.REACT_APP_BUILD_FOR_DESKTOP
import { IS_DESKTOP } from 'src/utils/constants'

const useStyles = makeStyles({
container: {
Expand Down Expand Up @@ -150,7 +149,7 @@ const CookiesBanner = (): ReactElement => {
setLocalAnalytics(acceptedAnalytics)
setLocalNecessary(acceptedNecessary)

if (acceptedAnalytics && !isDesktop) {
if (acceptedAnalytics && !IS_DESKTOP) {
loadGoogleAnalytics()
}
}
Expand All @@ -161,14 +160,14 @@ const CookiesBanner = (): ReactElement => {
const acceptCookiesHandler = async () => {
const newState = {
acceptedNecessary: true,
acceptedAnalytics: !isDesktop,
acceptedAnalytics: !IS_DESKTOP,
acceptedIntercom: true,
}
const cookieConfig: CookieAttributes = {
expires: 365,
}
await saveCookie(COOKIES_KEY, newState, cookieConfig)
setShowAnalytics(!isDesktop)
setShowAnalytics(!IS_DESKTOP)
setShowIntercom(true)
dispatch.current(openCookieBanner({ cookieBannerOpen: false }))
}
Expand Down Expand Up @@ -276,14 +275,14 @@ const CookiesBanner = (): ReactElement => {

return (
<>
{!isDesktop && !showIntercom && !isSafeAppView && (
{!IS_DESKTOP && !showIntercom && !isSafeAppView && (
<img
className={classes.intercomImage}
src={IntercomIcon}
onClick={() => dispatch.current(openCookieBanner({ cookieBannerOpen: true, intercomAlertDisplayed: true }))}
/>
)}
{!isDesktop && showBanner?.cookieBannerOpen && (
{!IS_DESKTOP && showBanner?.cookieBannerOpen && (
<CookiesBannerForm alertMessage={showBanner?.intercomAlertDisplayed} />
)}
</>
Expand Down
4 changes: 2 additions & 2 deletions src/logic/wallets/utils/walletList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { WalletInitOptions } from 'bnc-onboard/dist/src/interfaces'

import { getRpcServiceUrl, getDisabledWallets, _getChainId } from 'src/config'
import { WALLETS } from 'src/config/chain.d'
import { FORTMATIC_KEY, PORTIS_ID } from 'src/utils/constants'
import { IS_DESKTOP, FORTMATIC_KEY, PORTIS_ID } from 'src/utils/constants'

type Wallet = WalletInitOptions & {
desktop: boolean
Expand Down Expand Up @@ -72,7 +72,7 @@ const wallets = (): Wallet[] => {
}

export const getSupportedWallets = (): WalletInitOptions[] => {
if (window.isDesktop) {
if (IS_DESKTOP) {
return wallets()
.filter(({ desktop }) => desktop)
.filter(({ walletName }) => !getDisabledWallets().includes(walletName))
Expand Down
1 change: 0 additions & 1 deletion src/types/definitions.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ type Theme = typeof theme
export {}
declare global {
interface Window {
isDesktop?: boolean
ethereum?: {
autoRefreshOnNetworkChange: boolean
isMetaMask: boolean
Expand Down
1 change: 1 addition & 0 deletions src/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { CHAIN_ID } from 'src/config/chain.d'
export const APP_ENV = process.env.REACT_APP_ENV
export const NODE_ENV = process.env.NODE_ENV
export const IS_PRODUCTION = APP_ENV === 'production'
export const IS_DESKTOP = process.env.REACT_APP_BUILD_FOR_DESKTOP === 'true'
export const DEFAULT_CHAIN_ID = IS_PRODUCTION ? CHAIN_ID.ETHEREUM : CHAIN_ID.RINKEBY
export const PUBLIC_URL = process.env.PUBLIC_URL
export const TX_SERVICE_VERSION = '1'
Expand Down
Loading

0 comments on commit a15bac4

Please sign in to comment.