Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: use contextBridge in favour of requiring nodeIntegration #300

Merged
merged 4 commits into from
Jun 2, 2021

Conversation

matmalkowski
Copy link
Collaborator

Due to security concerns related to usage of nodeIntegration flag, according to best electron practices, renderer functions should be exposed with contextBridge. This PR does exactly that. It also changes a bit API to accomodate for this feature

Due to security concerns related to usage of nodeIntegration flag, according to best electron practices, renderer functions should be exposed with contextBridge. This PR does exactly that. It also changes a bit API to accomodate for this feature
@matmalkowski matmalkowski added enhancement ✨ security Pull requests that address a security vulnerability v2 All issues related to v2 labels Mar 2, 2021
@matmalkowski matmalkowski self-assigned this Mar 2, 2021
}

try {
contextBridge.exposeInMainWorld('__ElectronReduxBridge', bridge)
Copy link
Contributor

Choose a reason for hiding this comment

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

Do any of the functions exposed in the bridge allow a user to potentially gain access to the current store state, or any of the underlying BrowserWindow objects? Like I think fetchInitialState would be dangerous to expose as a global value. This could be worked around with the sort of solution used for preventDoubleInitialization but then that creates more problems if a user wants to reload the Redux environment without starting.

I think also exposing subscribeToIPCAction could be dangerous since it would let a malicious user pass any callback they wish to that function and read the data.


If I'm not mistaken about the dangers of exposing those functions, an alternative method would be a bindings approach, but I have to warn I've not actually tried implementing this for any library I've worked with yet.

I got the idea from: https://github.com/reZach/secure-electron-context-menu/blob/master/src/index.js

and then it could be used like this: https://github.com/reZach/secure-electron-context-menu#modify-your-preloadjs-file - although in the case of Electron-Redux I think we wouldn't need to expose anything via contextBridge except for composeWithStateSync - which I think is safe to expose?

What do you think?

Copy link
Contributor

Choose a reason for hiding this comment

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

We're just finishing up a release cycle here and starting to think about this again. @matmalkowski any thoughts on a path forward?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I got some internal review already, but I still don't understand the suggested bindings approach and how it would change the security vs this implementation. Would be great if you could provide some example with the difference in behavior we would see @slapbox

Copy link
Contributor

@Nantris Nantris May 28, 2021

Choose a reason for hiding this comment

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

Happy to discuss @matmalkowski!

The overarching concern with this implementation is the exposure of potentially sensitive or dangerous methods in the global scope.

fetchInitialState

The first thing that stands out to me is exposing fetchInitialState. Because this would end up mapped somewhere like window.api.fetchInitialState. If malicious code gained access within the application, exposing the entire state in the global scope gives the attackers free reign to access any and all data, at least the initial values.

Since Redux is supposed to be the "single source of truth," it inherently has a large portion of the application's data. The danger of this is going to depend on what sort of data is stored in the store, of course. Personal data, health data, financial data, etc., would ideally never be accessible from a global scope.

With the bindings approach, no functions to access the store's state would be exposed in a global scope which would make access to the store's state dramatically more difficult for an attacker.


subscribeToIPCAction

There's also probably a risk to having subscribeToIPCAction in the global scope. This potential attack I'll outline is just spitballing and could maybe be mitigated through other mechanisms, but there's sure to be other similar attacks someone more imaginative and intent on doing harm could find.

A potential attack I could envision is something like: an attacker could potentially register a function to send the action's payload via an xhr request whenever the IPC channel triggers, which for this particular IPC channel will be for every single Redux action. This would let an attacker access not just the initial state, but also have ongoing access to how the state develops over time.

The fact that the event arg is not being passed from the IPC channel here is very good to prevent access to Electron internals, but it still leaves application-specific vulnerabilities.

With that in mind, it appears my concern about a BrowserWindow potentially being exposed is moot.


The current Electron-Redux (master branch and alpha branch) don't allow attacks of either sort, so it seems like introducing access to those functions in the name of improving Electron security would be trading one type of risk for another.

I was very glad to see the Redux store removed from the global state in the main process on the alpha branch. It was my only security concern in the master branch, since it was trivial to access if an attacked managed to access the remote functionality and ran remote.getGlobal('getReduxState')(). The "hard" part of that attack was gaining access to remote. Putting the ability to access the state in the global scope of the renderer process makes it easier for an attacker to access the data than for them to need to gain access to the global scope of the main process.


With the bindings approach I think the only thing that would need to be exposed is composeWithStateSync which I can't imagine any potential attack vector for at this moment, but again, maybe a lack of imagination. If there is an attack vector involving composeWithStateSync then maybe there's no way to entirely prevent putting something dangerous in the global scope, but even if that's the case, the bindings approach would reduce the attack surface a lot. Is there anywhere in composeWithStateStync that an attacker would be able to access the store state or register a malicious action to the IPC listener that I'm overlooking?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Thanks for the writeup - I agree, the security risk created by this approach create actual regression and are not desired. I just didn't understand the bindings approach because I got confused by the idea that binding some functions will make things different from scope perspective. But the issues are basically addressed by making sure only the right functions are exposed, instead of the more raw ones. I took your example and moved to expose in preload script only the 2 required higher level functions as in composeWithStateSync and stateSyncEnhancer. I will commit the change in a sec, once I check the app is working as expected

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@slapbox so is 5c1b94e what we are looking for? :)

Copy link
Contributor

Choose a reason for hiding this comment

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

@matmalkowski I think this looks good! I'm sorry for the confusion I've caused by overthinking this/thinking that a minor tweak couldn't achieve the same end. I'm going to blame that on lack of intuition about how it all works together, though that's a pretty weak excuse!

I think that the only remaining point, from my perspective, is to make sure everyone agrees those functions don't expose any potential dangers. I don't see any potential dangers myself. I'm pretty sure that composeWithStateSync doesn't return anything potentially valuable to an attacker.

Copy link
Contributor

@Nantris Nantris May 29, 2021

Choose a reason for hiding this comment

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

Not a security thing, but would supporting a custom value here, in place of __ElectronReduxBridge, require changes to anything besides the preload function? I did a quick search and didn't see anything else that would need updating to support that.

By the way, thank you for your great work on this issue!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Not a security thing, but would supporting a custom value here, in place of __ElectronReduxBridge, require changes to anything besides the preload function? I did a quick search and didn't see anything else that would need updating to support that.

I don't know, probably could be done (but not as part of this PR though - what would be use case for it?

Copy link
Contributor

Choose a reason for hiding this comment

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

Really just a curiosity on my side/a developer experience thing, I shouldn't even have raised the distraction.

I can envision a future where a year from now the window object has 20 different keys added to it by various projects so I'm just trying to think ahead about that.

@matmalkowski matmalkowski marked this pull request as ready for review May 28, 2021 22:49
@matmalkowski matmalkowski added this to the 2.0 milestone May 30, 2021
Copy link
Contributor

@victorhqc victorhqc left a comment

Choose a reason for hiding this comment

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

Looks good to me! Just a small question about the ipcRenderer

examples/basic/src/main/index.ts Show resolved Hide resolved
src/main/forwardActionToRenderers.ts Show resolved Hide resolved
src/preload.ts Show resolved Hide resolved
src/renderer/forwardActionToMain.ts Show resolved Hide resolved
src/renderer/subscribeToIPCAction.ts Show resolved Hide resolved
@matmalkowski matmalkowski merged commit 83146af into alpha Jun 2, 2021
@matmalkowski matmalkowski deleted the contextBridge branch June 2, 2021 21:18
@matmalkowski
Copy link
Collaborator Author

🎉 This PR is included in version 2.0.0-alpha.9 🎉

The release is available on:

Your semantic-release bot 📦🚀

matmalkowski added a commit that referenced this pull request Oct 2, 2024
* feat: migrate to typescript (#259)

* feat: migrate to typescript

This is WIP

* feat: package.json cleanup

* chore: remove eslint config rules

* chore: more cleaning

* chore: add tsc tests

* chore: reorganize export structure

* chore: add some basic e2e tests

* buid: add GH action for PR

* fix: e2e test memory leak fix

* chore: vscode workspace settings

* fix: update username in package.json

* chore: rollup.config simplify

* chore: add eslint (#268)

* chore: add eslint

This PR adds eslint setup to the reposiory. It doesn't fix existing errors/warnings - should get addressed in follow up Pull Requests since the codebase isn't final now anyway. Errors so far:

```
/Users/maciej.malkowski/git/github.com/klarna/electron-redux/src/syncMain.ts
  18:56  error  Async arrow function has no 'await' expression  @typescript-eslint/require-await
  46:3   error  Unsafe return of an any typed value             @typescript-eslint/no-unsafe-return

/Users/maciej.malkowski/git/github.com/klarna/electron-redux/src/syncRenderer.ts
   21:8   error    Unsafe assignment of an any value                                                                @typescript-eslint/no-unsafe-assignment
   61:4   error    Unsafe return of an any typed value                                                              @typescript-eslint/no-unsafe-return
   76:3   error    Unsafe return of an any typed value                                                              @typescript-eslint/no-unsafe-return
   85:27  warning  Unexpected any. Specify a different type                                                         @typescript-eslint/no-explicit-any
   93:3   error    Promises must be handled appropriately or explicitly marked as ignored with the `void` operator  @typescript-eslint/no-floating-promises
  105:3   error    Unsafe return of an any typed value                                                              @typescript-eslint/no-unsafe-return
  105:32  warning  Unexpected any. Specify a different type                                                         @typescript-eslint/no-explicit-any

/Users/maciej.malkowski/git/github.com/klarna/electron-redux/src/utils/actions.ts
  15:31  warning  Missing return type on function  @typescript-eslint/explicit-module-boundary-types

/Users/maciej.malkowski/git/github.com/klarna/electron-redux/src/utils/json.ts
   6:9   error    Unsafe assignment of an any value                     @typescript-eslint/no-unsafe-assignment
   7:3   error    Unsafe member access .__hydrate_type on an any value  @typescript-eslint/no-unsafe-member-access
  22:24  warning  Missing return type on function                       @typescript-eslint/explicit-module-boundary-types
  22:36  warning  Argument 'value' should be typed with a non-any type  @typescript-eslint/explicit-module-boundary-types
  22:43  warning  Unexpected any. Specify a different type              @typescript-eslint/no-explicit-any
  23:6   error    Unsafe member access .__hydrate_type on an any value  @typescript-eslint/no-unsafe-member-access
  27:13  error    Unsafe member access .__hydrate_type on an any value  @typescript-eslint/no-unsafe-member-access
  28:18  error    Unsafe member access .items on an any value           @typescript-eslint/no-unsafe-member-access
  31:2   error    Unsafe return of an any typed value                   @typescript-eslint/no-unsafe-return

/Users/maciej.malkowski/git/github.com/klarna/electron-redux/src/utils/misc.ts
   7:44  warning  Missing return type on function  @typescript-eslint/explicit-module-boundary-types
  24:29  warning  Missing return type on function  @typescript-eslint/explicit-module-boundary-types
  33:31  warning  Missing return type on function  @typescript-eslint/explicit-module-boundary-types

/Users/maciej.malkowski/git/github.com/klarna/electron-redux/tests/e2e.spec.ts
  21:5   error  Unexpected `await` of a non-Promise (non-"Thenable") value  @typescript-eslint/await-thenable
  36:12  error  Unexpected `await` of a non-Promise (non-"Thenable") value  @typescript-eslint/await-thenable
  45:12  error  Unexpected `await` of a non-Promise (non-"Thenable") value  @typescript-eslint/await-thenable

/Users/maciej.malkowski/git/github.com/klarna/electron-redux/tests/e2e/main/index.ts
  31:5   error  Promises must be handled appropriately or explicitly marked as ignored with the `void` operator  @typescript-eslint/no-floating-promises
  31:44  error  Invalid type "string | undefined" of template literal expression                                 @typescript-eslint/restrict-template-expressions
  33:5   error  Promises must be handled appropriately or explicitly marked as ignored with the `void` operator  @typescript-eslint/no-floating-promises
  54:1   error  Promises must be handled appropriately or explicitly marked as ignored with the `void` operator  @typescript-eslint/no-floating-promises

/Users/maciej.malkowski/git/github.com/klarna/electron-redux/tests/e2e/renderer/index.ts
   9:5  warning  Forbidden non-null assertion  @typescript-eslint/no-non-null-assertion
  18:5  warning  Forbidden non-null assertion  @typescript-eslint/no-non-null-assertion
  22:5  warning  Forbidden non-null assertion  @typescript-eslint/no-non-null-assertion
  32:5  warning  Forbidden non-null assertion  @typescript-eslint/no-non-null-assertion

/Users/maciej.malkowski/git/github.com/klarna/electron-redux/tests/typescript/syncMain.ts
  5:7  warning  'store' is assigned a value but never used  @typescript-eslint/no-unused-vars

/Users/maciej.malkowski/git/github.com/klarna/electron-redux/tests/typescript/syncRenderer.ts
  5:7  warning  'store' is assigned a value but never used  @typescript-eslint/no-unused-vars

✖ 35 problems (20 errors, 15 warnings)
```

* fix: fix test related type issues

* chore: add prettier & format the code (#269)

This PR adds the prettier code formatter with custom config, and formats existing code according to those rulles.

Pre-commit hook to format staged files was also added.

* feat: add Code of Conduct & bump version (#270)

This pull request adds Code of Conducts and marks the change as breaking for semanric release to
bump the version to v2

BREAKING CHANGE: TS rewrite

* feat: add semantic-release setup for automatic deployments (#271)

* feat: add semantic-release setup for automatic deployments

This adds the setup for semantic-release. master/beta/alpha branch changes will get deployed to npm
with version depending on the commit message

* chore: add PR linter

* chore: add more events tro trigger the check

* fix: remove default custom serialization and make it an opt-in option (#272)

Removing custom serializer as a default, since its not alligned with redux recomendation about store
& serialization

fix #261

* fix: reduce bundle size by limiting external dependencies (#276)

I removed external dependency of `flux-standard-action` that includes entire loadash with it when imported. Instead, I re-implemented single function we are using and relied on single modules from loadash that are required. Should reduce the size significantly

* feat: ignore devtools when sending actions to renderers (#275)

Co-authored-by: Maciej Małkowski <monkey3310@gmail.com>

* chore: add README & LICENSE (#281)

* chore: add README & LICENSE

As per title, adding initial README file & LICENSE. It will need some work for sure, I've left the missing links as TODO

* chore: more badges

* chore: apply suggestions from code review

Co-authored-by: Burkhard Reffeling <burkhard.reffeling@gmail.com>

* Update LICENSE.md

Co-authored-by: Burkhard Reffeling <burkhard.reffeling@gmail.com>

* feat: make renderer state initialization synchronous by default (#280)

* feat: make renderer state initialization synchronous by default

Implements #278

* code review comments

* chore: spelling

Co-authored-by: Burkhard Reffeling <burkhard.reffeling@gmail.com>

* chore: code review comments

Co-authored-by: Burkhard Reffeling <burkhard.reffeling@gmail.com>

* docs: setup base documentation website  (#277)

* docussaurus initial setup

* base docussaurus config

* add base gh-pages deployment action

* remove unnecessary job step

* test changing baseurl

* fix last commit

* fix last commit

* remove trailing slash

* fix baseUrl config

* add basic content based on readme

* use yarn in gh-pages action

Co-authored-by: Maciej Małkowski <monkey3310@gmail.com>

* feat: add option for denyList (#274)

* fix: align enhancer style with standards to wrap the middleware (#284)

* fix: align enhancer style with standards to wrap the middleware

* fix: align enhancer style with standards to wrap the middleware

* docs: adding initial content for v2 (#290)

* docs: adding initial content for v2

Adding some initial content for Version 2, including expected file tree of documentation & legacy v1 docs paragraph.

* chore: apply suggestions from code review

Co-authored-by: Burkhard Reffeling <burkhard.reffeling@gmail.com>

Co-authored-by: Burkhard Reffeling <burkhard.reffeling@gmail.com>

* docs: fixing doc build issue with missing paths (#295)

Also adding PR build step to prevent this from happening again

* chore: update semrel lint to run on forks

* fix(#285): add composeWithStateSync to resolve issues with enhancer order (#296)

* fix(#285): add composeWithStateSync to resolve issues with enhancer order

* fix: resolve PR comments

* chore: add type tests

* docs: update README getting started  and add changes to FAQ

Co-authored-by: Maciej Małkowski <monkey3310@gmail.com>

* chore: update README

* chore: update README links

* chore: adding basic example of usage in raw scenario + moving integration tests (#299)

* chore: adding basic example of usage in raw scenario

* chore: work in progress

* chore: trying to add tests

* chore: fixing e2e tests and making them runnable

* fix package.json

* chore: code review comments addressed

* feat: use contextBridge in favour of requiring nodeIntegration (#300)

* feat: use contextBridge in favour of requiring nodeIntegration

Due to security concerns related to usage of nodeIntegration flag, according to best electron practices, renderer functions should be exposed with contextBridge. This PR does exactly that. It also changes a bit API to accomodate for this feature

* feat: fixing issues with test enviroment

* fix: add missing  preventDoubleInitialization() check

* change the scope of the contextBridge bindings to only expose high level API

---------

Co-authored-by: Paul Tiedtke <PaulTiedtke@web.de>
Co-authored-by: Burkhard Reffeling <burkhard.reffeling@gmail.com>
Co-authored-by: António Almeida <theantonioalmeida@gmail.com>
Co-authored-by: Jonas Snellinckx <jo.snel@hotmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement ✨ released on @alpha security Pull requests that address a security vulnerability v2 All issues related to v2
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[v2] Prefer using preload script for renderer dependencies - no longer require nodeIntegration
3 participants