Skip to content
Permalink
Browse files

chore: increase security of default_app (#17318)

  • Loading branch information...
miniak authored and MarshallOfSound committed Mar 11, 2019
1 parent 188d311 commit a8698d092b9381e00213122e852f63105219bda1
@@ -1,4 +1,4 @@
import { app, BrowserWindow, BrowserWindowConstructorOptions } from 'electron'
import { app, dialog, BrowserWindow, shell, ipcMain } from 'electron'
import * as path from 'path'

let mainWindow: BrowserWindow | null = null
@@ -8,18 +8,52 @@ app.on('window-all-closed', () => {
app.quit()
})

export const load = async (appUrl: string) => {
function decorateURL (url: string) {
// safely add `?utm_source=default_app
const parsedUrl = new URL(url)
parsedUrl.searchParams.append('utm_source', 'default_app')
return parsedUrl.toString()
}

// Find the shortest path to the electron binary
const absoluteElectronPath = process.execPath
const relativeElectronPath = path.relative(process.cwd(), absoluteElectronPath)
const electronPath = absoluteElectronPath.length < relativeElectronPath.length
? absoluteElectronPath
: relativeElectronPath

const indexPath = path.resolve(app.getAppPath(), 'index.html')

function isTrustedSender (webContents: Electron.WebContents) {
if (webContents !== (mainWindow && mainWindow.webContents)) {
return false
}

const parsedUrl = new URL(webContents.getURL())
return parsedUrl.protocol === 'file:' && parsedUrl.pathname === indexPath
}

ipcMain.on('bootstrap', (event) => {
try {
event.returnValue = isTrustedSender(event.sender) ? electronPath : null
} catch {
event.returnValue = null
}
})

async function createWindow () {
await app.whenReady()

const options: BrowserWindowConstructorOptions = {
const options: Electron.BrowserWindowConstructorOptions = {
width: 900,
height: 600,
autoHideMenuBar: true,
backgroundColor: '#FFFFFF',
webPreferences: {
preload: path.resolve(__dirname, 'preload.js'),
contextIsolation: true,
preload: path.resolve(__dirname, 'renderer.js'),
webviewTag: false
sandbox: true,
enableRemoteModule: false
},
useContentSize: true,
show: false
@@ -30,9 +64,39 @@ export const load = async (appUrl: string) => {
}

mainWindow = new BrowserWindow(options)

mainWindow.on('ready-to-show', () => mainWindow!.show())

mainWindow.webContents.on('new-window', (event, url) => {
event.preventDefault()
shell.openExternal(decorateURL(url))
})

mainWindow.webContents.session.setPermissionRequestHandler((webContents, permission, done) => {
const parsedUrl = new URL(webContents.getURL())

const options: Electron.MessageBoxOptions = {
title: 'Permission Request',
message: `Allow '${parsedUrl.origin}' to access '${permission}'?`,
buttons: ['OK', 'Cancel'],
cancelId: 1
}

dialog.showMessageBox(mainWindow!, options, (response) => {
done(response === 0)
})
})

return mainWindow
}

export const loadURL = async (appUrl: string) => {
mainWindow = await createWindow()
mainWindow.loadURL(appUrl)
mainWindow.focus()
}

export const loadFile = async (appPath: string) => {
mainWindow = await createWindow()
mainWindow.loadFile(appPath)
mainWindow.focus()
}
@@ -2,9 +2,10 @@

<head>
<title>Electron</title>
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self'" />
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self'; connect-src 'self'" />
<link href="./styles.css" type="text/css" rel="stylesheet" />
<link href="./octicon/build.css" type="text/css" rel="stylesheet" />
<script defer src="./index.js"></script>
</head>

<body>
@@ -52,31 +53,31 @@

<nav>
<div class="linkcol">
<a class="hero-link" href="https://electronjs.org/blog">
<a class="hero-link" target="_blank" href="https://electronjs.org/blog">
<span class="octicon hero-octicon octicon-gist" aria-hidden="true"></span>
<h4>Blog</h4>
</a>
</div>
<div class="linkcol">
<a class="hero-link" href="https://github.com/electron/electron">
<a class="hero-link" target="_blank" href="https://github.com/electron/electron">
<span class="octicon hero-octicon octicon-mark-github" aria-hidden="true"></span>
<h4>Repository</h4>
</a>
</div>
<div class="linkcol">
<a class="hero-link" href="https://electronjs.org/docs">
<a class="hero-link" target="_blank" href="https://electronjs.org/docs">
<span class="octicon hero-octicon octicon-gear" aria-hidden="true"></span>
<h4>Docs</h4>
</a>
</div>
<div class="linkcol">
<a class="hero-link" href="https://github.com/electron/electron-api-demos">
<a class="hero-link" target="_blank" href="https://github.com/electron/electron-api-demos">
<span class="octicon hero-octicon octicon-star" aria-hidden="true"></span>
<h4>API Demos</h4>
</a>
</div>
<div class="linkcol">
<a class="hero-link" href="https://electronforge.io">
<a class="hero-link" target="_blank" href="https://electronforge.io">
<span class="octicon hero-octicon octicon-gift" aria-hidden="true"></span>
<h4>Forge</h4>
</a>
@@ -0,0 +1,30 @@
async function getOcticonSvg (name: string) {
try {
const response = await fetch(`octicon/${name}.svg`)
const div = document.createElement('div')
div.innerHTML = await response.text()
return div
} catch {
return null
}
}

async function loadSVG (element: HTMLSpanElement) {
for (const cssClass of element.classList) {
if (cssClass.startsWith('octicon-')) {
const icon = await getOcticonSvg(cssClass.substr(8))
if (icon) {
for (const elemClass of element.classList) {
icon.classList.add(elemClass)
}
element.before(icon)
element.remove()
break
}
}
}
}

for (const element of document.querySelectorAll<HTMLSpanElement>('.octicon')) {
loadSVG(element)
}
@@ -129,9 +129,14 @@ function showErrorMessage (message: string) {
process.exit(1)
}

async function loadApplicationByUrl (appUrl: string) {
const { load } = await import('./default_app')
load(appUrl)
async function loadApplicationByURL (appUrl: string) {
const { loadURL } = await import('./default_app')
loadURL(appUrl)
}

async function loadApplicationByFile (appPath: string) {
const { loadFile } = await import('./default_app')
loadFile(appPath)
}

function startRepl () {
@@ -156,13 +161,9 @@ if (option.file && !option.webdriver) {
const protocol = url.parse(file).protocol
const extension = path.extname(file)
if (protocol === 'http:' || protocol === 'https:' || protocol === 'file:' || protocol === 'chrome:') {
loadApplicationByUrl(file)
loadApplicationByURL(file)
} else if (extension === '.html' || extension === '.htm') {
loadApplicationByUrl(url.format({
protocol: 'file:',
slashes: true,
pathname: path.resolve(file)
}))
loadApplicationByFile(path.resolve(file))
} else {
loadApplicationPackage(file)
}
@@ -196,10 +197,5 @@ Options:
console.log(welcomeMessage)
}

const indexPath = path.join(__dirname, '/index.html')
loadApplicationByUrl(url.format({
protocol: 'file:',
slashes: true,
pathname: indexPath
}))
loadApplicationByFile('index.html')
}
@@ -0,0 +1,20 @@
import { ipcRenderer } from 'electron'

function initialize () {
const electronPath = ipcRenderer.sendSync('bootstrap')

function replaceText (selector: string, text: string) {
const element = document.querySelector<HTMLElement>(selector)
if (element) {
element.innerText = text
}
}

replaceText('.electron-version', `Electron v${process.versions.electron}`)
replaceText('.chrome-version', `Chromium v${process.versions.chrome}`)
replaceText('.node-version', `Node v${process.versions.node}`)
replaceText('.v8-version', `v8 v${process.versions.v8}`)
replaceText('.command-example', `${electronPath} path-to-app`)
}

document.addEventListener('DOMContentLoaded', initialize)

This file was deleted.

Oops, something went wrong.
@@ -94,8 +94,9 @@ filenames = {

default_app_ts_sources = [
"default_app/default_app.ts",
"default_app/index.ts",
"default_app/main.ts",
"default_app/renderer.ts",
"default_app/preload.ts",
]

default_app_static_sources = [
@@ -158,15 +158,15 @@ const errorUtils = require('@electron/internal/common/error-utils')
// since browserify won't try to include `electron` in the bundle, falling back
// to the `preloadRequire` function above.
function runPreloadScript (preloadSrc) {
const preloadWrapperSrc = `(function(require, process, Buffer, global, setImmediate, clearImmediate) {
const preloadWrapperSrc = `(function(require, process, Buffer, global, setImmediate, clearImmediate, exports) {
${preloadSrc}
})`

// eval in window scope
const preloadFn = binding.createPreloadScript(preloadWrapperSrc)
const { setImmediate, clearImmediate } = require('timers')

preloadFn(preloadRequire, preloadProcess, Buffer, global, setImmediate, clearImmediate)
preloadFn(preloadRequire, preloadProcess, Buffer, global, setImmediate, clearImmediate, {})
}

for (const { preloadPath, preloadSrc, preloadError } of preloadScripts) {

0 comments on commit a8698d0

Please sign in to comment.
You can’t perform that action at this time.