Skip to content
This repository has been archived by the owner on Jan 4, 2019. It is now read-only.

Need an advice for building app with communication between renderer and main #393

Open
3n-mb opened this issue Nov 26, 2017 · 32 comments
Open

Comments

@3n-mb
Copy link
Contributor

3n-mb commented Nov 26, 2017

What is the best way for communication between renderer and main? Please advise.

  1. Does muon have an ipc like electron?
  2. Is it possible to pass byte arrays via ipc without being JSON-ified?
  3. Electron's rmi is not present in muon, is it?
  4. There is an ipc module for electron, that did the following trick. For actions, initiated on main side, contents.executeJavaScript(...) is used. For actions, initiated on renderer side, custom protocol is used. Will these work in muon?
  5. Is preload available in muon?

Thank you, in advance.

@3n-mb 3n-mb closed this as completed Nov 26, 2017
@3n-mb 3n-mb reopened this Nov 26, 2017
@posix4e
Copy link
Contributor

posix4e commented Nov 27, 2017

This needs to be done very carefully. Annoyingly I am looking to make the sandboxing between the two components thicker not thinner. However, there's no reason why you can't share a socket

@3n-mb
Copy link
Contributor Author

3n-mb commented Nov 27, 2017

@posix4e

there's no reason why you can't share a socket

I want it to also work on windows, and, my assumption is that there are no unix sockets there. Am I wrong?

@3n-mb
Copy link
Contributor Author

3n-mb commented Nov 27, 2017

@posix4e wait a second, can I get socket on a renderer side in a preload script in muon, with full sandbox on?

@3n-mb
Copy link
Contributor Author

3n-mb commented Nov 27, 2017

@posix4e you are talking about named pipes on Windows, aren't you? I can channel all communication through this.

Can I get a socket on a renderer side in a preload? Muon docs say that node is eradicated from renderer. How can I get the socket?

@posix4e
Copy link
Contributor

posix4e commented Nov 28, 2017

Ahh I was a bit confused. So you are trying to do this in javascript or change the c++?

@3n-mb
Copy link
Contributor Author

3n-mb commented Nov 28, 2017

@posix4e yes, I prefer to do this in javascript land, without a need to custom compile muon.

I'll introduce context. Its for 3NWeb platform effort. Client side core, currently an Electron, does crypto for messaging, storage, identity.
Core (main process) also starts apps in their own renderer processes.
App has w3n object injected into its global space (preload is used for this on Electron). This capabilities' object provides to an app only those capabilities that an app is allowed to use. Chat app may be allowed to send messages, and have own storage, synced, local to machine, or both. Storage app may be allowed to access user's files, synced, local, and from device fs. But storage doesn't get to send messages.
Neither storage, nor chat apps have reasons to talk to random sites, so their sessions don't serve http:// at all. Of cause, there is no file://, no chrome://, etc.

We do want to provide explicitly defined capabilities to an app, but we want to contain an app. Firstly, to guard against our own bugs. Secondly, apps may will come from 3rd party developers from the web with no centralized app store (yey!).
Hence, we want as much sandboxing as possible, with an ability to give in renderer a capability w3n object.

Capabilities object is sort of a proxy to core's functionality, that an app is allowed to use. All of communication to animate such proxy, can be passed via socket. In Electron, we had a version that used electron's ipc, but its JSON-ification is the only nuisance, when passing Uint8Array's.

Muon's story sounds like we can get true sandboxing, by jumping from Electron to Muon. To make this jump, a communication channel is needed between main and renderer. But how can it be arranged on muon, so that it drives w3n capabilities proxy object on a renderer side?

@shadowcodex
Copy link
Contributor

If you use chrome://brave/ + local path it exposes chrome.ipcRenderer which can call IPC. Main thread can listen to IPC commands and pick up messages that way.

@3n-mb
Copy link
Contributor Author

3n-mb commented Jan 8, 2018

@shadowcodex thank you for the tip

@kewde
Copy link

kewde commented Jan 11, 2018

@shadowcodex

Do you have more information about this please?
I've been undergoing the effort of writing a boilerplate and tutorial for sandboxing on electron.
The state of security in electron is worrisome, hence I could use a few people with the right knowledge to trickle it down to the application developers.

Is chrome.ipcRenderer exposed to the whole renderer process or is it restricted to the preload environment?

If it's exposed to the whole of the renderer than an attacker can still leverage it to do damage, a whitelist on the channels should be enforced.
Currently for vanilla electron I have the following preload script that will act as a IPC channel filter:
https://github.com/kewde/electron-sandbox/blob/master/sandbox-extended/preload-extended.js

I was looking into muon to ditch electron, because I've been experiencing some odd behaviour:
kewde/electron-sandbox-boilerplate#3
There seems to be a conflicting arguments passed= --no-sandbox ... --enable-sandbox.

The repository that has all my research is:
https://github.com/kewde/electron-sandbox
I'm looking for contributors with the right knowledge to help me nail it, once and for all.

@kewde
Copy link

kewde commented Jan 11, 2018

A quick thought I just had, maybe it is possible to have the preload environment copy/reference the chrome.ipcRenderer to a local object of the filter, and then override chrome.ipcRenderer. The filter then attaches to the window and if I'm lucky, everything works.

@3n-mb
Copy link
Contributor Author

3n-mb commented Jan 11, 2018

@kewde your code is good, if javascript closure can't be busted in a meaning mentioned/discussed here. If it were 100% bullet proof, electron would've been fine, I guess.

@kewde we must do this kind of "squeaking to get needed oil".

Well deserved rant

Every single time @diracdeltas sends on twitter a reminder that electron's security story isn't good, we come here, to muon place. And again, we see no actionable docs and no examples, of how we can use muon with some communication between main and renderer (compartmentalization of an app's sections), i.e. not just for displaying web pages (brave's specific use case).
It has been like this for almost a year now, and it starts to feel like a form of torture (I wrote a whole paragraph here. Took 20 breaths, walked around the office, and deleted it.).

@diracdeltas
Copy link
Member

Is chrome.ipcRenderer exposed to the whole renderer process or is it restricted to the preload environment?

It's restricted to extension contexts, such as chrome-extension:// pages and content scripts.

If you're in a privileged non-webview renderer context like the Electron window context, use electron.ipcRenderer instead.

Then to receive these messages these messages in the main process, use electron.ipcMain.on

it's often easiest to just look at the source code in brave/browser-laptop

@3n-mb
Copy link
Contributor Author

3n-mb commented Jan 11, 2018

@diracdeltas does it mean, that

  • In order to inject some utility object into renderer with less-trusted code, we have to use chrome extension, instead of preload script? For this reason, does preload play a lesser role in muon, or has smaller utility?
  • Can you give a more specific examples in brave/browser-laptop. Thank you.

@3n-mb 3n-mb closed this as completed Jan 11, 2018
@kewde
Copy link

kewde commented Jan 11, 2018

@diracdeltas
Honestly, I haven't ever touched extension contexts, are they properly sandboxed?
Is chrome.ipcRenderer the only hazardous/privileged function there?
I don't know enough about this sadly.

I know I'm probably asking for a lot, but would it be possible to extend this to the preload context?

Ideally, it is possible to have a completely normal and sandboxed environment, with one addition: ipcRenderer exposed with a whitefilter forced by the browser/main process.

@3n-mb
Honestly, I will pay someone to do the above and get it into muon, I'll put my money where my mouth is on this one.
Ping me in this issue if you're interested and capable.

@3n-mb 3n-mb reopened this Jan 11, 2018
@diracdeltas
Copy link
Member

diracdeltas commented Jan 11, 2018

In order to inject some utility object into renderer with less-trusted code, we have to use chrome extension, instead of preload script? For this reason, does preload play a lesser role in muon, or has smaller utility?

Muon supports the majority of Chrome extension APIs and has added some extra ones like chrome.ipcRenderer. So instead of using preload scripts you can use content scripts, same as for Chrome extensions: https://developer.chrome.com/extensions/content_scripts. I don't know if Electron's preload scripts are still supported but I think most of the functionality should be doable via content scripts.

extension contexts are privileged - they cannot be modified from the regular page context. chrome.ipcRenderer is the communication channel to other processes.

@3n-mb
Copy link
Contributor Author

3n-mb commented Jan 11, 2018

Honestly, I will pay someone to do the above and get it into muon, I'll put my money where my mouth is on this one.

@kewde I think that muon folks already have a know-how that is why I am bugging them. So, @diracdeltas and folks are the ones to ping with this offer.

@kewde
Copy link

kewde commented Jan 11, 2018

Ahh, this is interesting.
Thank you @diracdeltas, I think I'll manage from here on.

Although the execution environments of content scripts and the pages that host them are isolated from each other, they share access to the page's DOM. If the page wishes to communicate with the content script (or with the extension via the content script), it must do so through the shared DOM.

I will work on implementing this in electron-sandbox once I find enough time (and courage).
@3n-mb if by the time I get to it, you write some code, please share it with me.

@3n-mb
Copy link
Contributor Author

3n-mb commented Jan 11, 2018

@diracdeltas

1)

So instead of using preload scripts you can use content scripts

If I want security, should word can be must, in muon?
In general, can in docs and apis is ambiguous.

2)

I don't know if Electron's preload scripts are still supported

Are you talking about muon? ... guys, do you have inner-team communication?

3)

extension contexts are privileged - it cannot be modified by a regular page context.

This is talking business. And this must be in docs. And, ... can you kill electron's docs, as misleading docs are worse than no-docs.

@kewde
Copy link

kewde commented Jan 11, 2018

On a note, my offer for exposing an IPC to the renderer still stands by the way.
I'll look into content scripts and extensions but If I'm allowed to dream, it would be nice to have something like this:

app.on('ready', () => {
  win = new BrowserWindow({
    webPreferences: {
      nodeIntegration: false,
      sandbox: true,
      contextIsolation: true,
       ipcRendererWhitelist: [
              "whitelisted channel",
              ...
       ]
    }
  })
  win.loadURL('file://' + __dirname + '/index.html')
})

Note: I made up ipcRendererWhitelist (in before I confuse a stranger).

and then an exposed electron.ipcRenderer in the renderer process.

Also, interesting:
https://developer.chrome.com/extensions/sandboxingEval

@3n-mb
Copy link
Contributor Author

3n-mb commented Jan 11, 2018

@kewde
You want to make an extension, that will insert some object into renderer's window. Your script should be an extension, not a preload.

As for nodeIntegration and sandbox these are not applicable to muon at all. And yes, this is not written in docs that were transferred verbatim from electron (hence, fuming).

Your extension organizes the whitelist by virtue of giving to renderer particular objects, via shared DOM.
@diracdeltas is this assessment correct?

@kewde
Copy link

kewde commented Jan 11, 2018

@3n-mb
Yeah basically, we'll be writing extensions.

Extensions provide the ability of sandboxed files/frames which is what we'll want to use.
The extension has a main (hidden) background.html which is basically the almighty one I guess.
That one can spawn other frames.
The sandboxed file can share the DOM with the content script.
The content script is able to do IPC messaging but limited.

extension contexts are privileged - they cannot be modified from the regular page context.

I'm not sure if the sandboxed file/frame can communicate with the extension directly, or if it's forced to go through content scripts. There's a lot of figuring out to do, but at least we have a direction.
That's my 10 minute research, I have digging todo.

@bridiver
Copy link
Collaborator

to communicate between extensions and background pages you should use the chrome api https://developer.chrome.com/extensions/runtime#method-sendMessage
the ipc api will be limited to component extensions and chrome://brave pages

@samuelmaddock
Copy link
Contributor

samuelmaddock commented Jan 24, 2018

@3n-mb @kewde check out the documentation I wrote in #444. It goes over how to closely mimic preload scripts using content scripts.

Communication channels could look something like this

Main process <--IPC--> Content Script <--Custom DOM Event--> Injected JavaScript

If you don't need to mutate global window properties, you might not need to inject any JavaScript from the content script.

@3n-mb
Copy link
Contributor Author

3n-mb commented Jan 24, 2018

@samuelmaddock

First, after reading this doc, I have following questions:

  • Given that in things loaded from chrome://brave/*, there will be ipcRenderer, remote, and webFrame, and, given that preload scripts have been removed in favor of Chrome extension content scripts, are extensions loaded with chrome://brave/*, to have ipcRenderer, remote, and webFrame available?
    Is it happening by default with extensions?
    It would be lovely to have a comparison between preload, and respective extension that needs to be written instead. Such example must also have settings for including extension into app's build.
  • Since chrome://brave/* has some prominence, does it mean that making a session without chrome protocol schema will make it impossible to have extensions with access to ipcRenderer?

In my particular use case, I want the following (using ASCII graphics):

Main proc <--IPC--> preload/extension -- injects capability object --> Untrusted Content Script

The best IPC in a web world is that to web worker, because it allows to pass byte array without mangling it (why stick json-ification everywhere?).
This non-mangling of byte array can be achieved in electron world with ... rmi, i.e. use of remote. But rmi is much bigger gun than mere ipc.

Can there be an example of how to pass byte arrays between main and extension with minimal interference? ipcRenderer? It json-ifies byte array 😢

@samuelmaddock
Copy link
Contributor

@3n-mb If you can't use IPCs for byte arrays, the next best idea I can think of is a local WebSocket server initialized from the main process. Then create a client in the extension script so you send binary data between the two.

@3n-mb
Copy link
Contributor Author

3n-mb commented Jan 24, 2018

@samuelmaddock
Somehow ipc over localhost network seems not very private. ipc should be between these two processes, no snooping. Less possibilities for snooping => more security.

@samuelmaddock
Copy link
Contributor

Also, if you load an extension using 'component' for the last parameter, the remote API will also be available in the content script on the chrome.* object.

session.defaultSession.extensions.load(extPath, manifest, 'component');

There was some talk about documenting that in #417 on the Discord #developers-muon channel.

@3n-mb
Copy link
Contributor Author

3n-mb commented Jan 24, 2018

@samuelmaddock
My understanding is that remote is here to be used.
An additional issue here is that sometimes you want to have a weak reference (in contrast WeakMap hides needed info). It seems that remote, as rmi, handles references, and removal of those across renderer-main boundary. Modules like weak work only on main side, and not on renderer (I don't get the principle here), begging for rmi+weak combination.
rmi seems to be a surface for reduction-for-security. But, as it looks now, we need to hope that muon ensure rmi security. And my particular use case will have to use rmi in extension, as the only option. Extension will be shielding raw rmi access from content script(s).

@3n-mb
Copy link
Contributor Author

3n-mb commented Jan 24, 2018

@samuelmaddock
There need to be preload to extension/component example. It seems that there can be many options of how things can be done, and one canonical example would direct devs like myself into right direction.

@3n-mb
Copy link
Contributor Author

3n-mb commented Jan 24, 2018

@samuelmaddock
May be a sample of extension/component for use in muon app? Showing how it is added in the app itself.

@kewde
Copy link

kewde commented Jan 24, 2018

@samuelmaddock

Thanks for that descriptive guide, there were some things I didn't know before.
I will be including it in the repositories I've been building 👍

Is it an active community on the #developers-muon? I don't really like Discord tbh.
A friend of mine runs a bot that can hook up Slack, Discord, IRC and Matrix.
I guess I'll hop in and see if I can get them to expand it.

@3n-mb
Copy link
Contributor Author

3n-mb commented Feb 6, 2018

I want to put for the record, that question 2: "Is it possible to pass byte arrays via ipc without being JSON-ified?" isn't applicable for current code.

It used to be the case at least for June 2016 (just date-checked my repos). Today Uint8Arrays are not JSON-ified in Electron, even if they sit inside of an object. And the same code is present in muon (e.g.).

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants