Ad

Electron: How To Securely Inject Global Variable Into BrowserWindow / BrowserView?

- 1 answer

I want to load an external webpage in Electron using BrowserView. It has pretty much the same API as BrowserWindow.

const currentWindow = remote.getCurrentWindow();
const view = new remote.BrowserView({
  webPreferences: {
    // contextIsolation: true,
    partition: 'my-view-partition',
    enableRemoteModule: false,
    nodeIntegration: false,
    preload: `${__dirname}/preload.js`,
    sandbox: true,
  },
});
view.setAutoResize({ width: true, height: true });
view.webContents.loadURL('http://localhost:3000');

In my preload.js file, I simply attach a variable to the global object.

process.once('loaded', () => {
  global.baz = 'qux';
});

The app running on localhost:3000 is a React app which references the value like this:

const sharedString = global.baz || 'Not found';

The problem is I have to comment out the setting contextIsolation: true when creating the BrowserView. This exposes a security vulnerability.

Is it possible to (one way - from Electron to the webpage) inject variables into a BrowserView (or BrowserWindow) while still using contextIsolation to make the Electron environment isolated from any changes made to the global environment by the loaded content?

Update: One possible approach could be intercepting the network protocol, but I'm not sure about this 🤔

app.on('ready', () => {
  const { protocol } = session.fromPartition('my-partition')

  protocol.interceptBufferProtocol('https', (req, callback) => {
    if (req.uploadData) {
      // How to handle file uploads?
      callback()
      return
    }

    // This is electron.net, docs: https://electronjs.org/docs/api/net
    net
      .request(req)
      .on('response', (res) => {
        const chunks = []
        res.on('data', (chunk) => {
          chunks.push(Buffer.from(chunk))
        })
        res.on('end', () => {
          const blob = Buffer.concat(chunks)
          const type = res.headers['content-type'] || []
          if (type.includes('text/html') && blob.includes('<head>')) {
            // FIXME?
            const pos = blob.indexOf('<head>')
            // inject contains the Buffer with the injected HTML script
            callback(Buffer.concat([blob.slice(0, pos), inject, blob.slice(pos)]))
          } else {
            callback(blob)
          }
        })
      })
      .on('error', (err) => {
        console.error('error', err)
        callback()
      })
      .end()
  })
})
Ad

Answer

Other answers are outdated, use contextBridge be sure to use sendToHost() instead of send()

    // Preload (Isolated World)
    const { contextBridge, ipcRenderer } = require('electron')
    
    contextBridge.exposeInMainWorld(
      'electron',
      {
        doThing: () => ipcRenderer.sendToHost('do-a-thing')
      }
    )
    
    // Renderer (Main World)
    
    window.electron.doThing()
Ad
source: stackoverflow.com
Ad