I'm making a game in Electron and I want it to support mods. Sometimes those mods will need to use custom logic, which leaves me with the issue of dynamically loading their code and I'm finding it hard to come up with a way to do that securely.
What I've considered
Ideally, I'd like to execute the mod scripts while passing just the few safe game objects they need as parameters, but that seems to be impossible (no matter the solution, that code can still access the global scope).
Most importantly, I have to prevent the untrusted code from accessing the preload global scope (with Node APIs), so require or anything else done in the preload is out the window.
Therefore that code has to be executed in the renderer.
My solution so far
I can either read the files in preload using fs or directly in the renderer using fetch. I have nodeIntegration set to false and contextIsolation set to true, and trusted code loaded by the preload script is selectively passed to the renderer through a contextBridge. The code which accesses Node APIs is properly encapsulated.
Unfortunately, that still leaves me with having to execute the unsafe code somehow, and I don't think there's any other way than to use eval or Function. Even though malicious code could not access Node APIs, it would still have full access to the renderer global scope, leaving the application vulnerable to, for example, a prototype pollution attack.
To sum up:
- The safer place to execute untrusted code is clearly in the renderer
- There is no alternative to using
evalorFunction - This leaves the
rendererglobal scope vulnerable to attacks which I can try to mitigate but can never make it completely safe
My first question: are these assumptions true, or is there a better way to do it?
The risks and how to mitigate them
So the potentially malicious code has access to the renderer global scope. What's the risk?
Well, any sensitive user data will be safely stored in the preload, the same goes for access to the user's computer with Node APIs. The attacker can break the game (as in, the current 'session'), but I can catch any errors caused by that and reload the game with the malicious mod turned off. The global scope will only hold the necessary constructors and no actual instances of the game's classes. It seems somewhat safe, the worst thing that could happen is a reload of the game.
My second question: am I missing anything here regarding the risks?
My third question: are there any risks of using eval or Function that I'm not thinking of? I've sort of been bombarded with "eval bad" ever since I've started getting into JS and now I feel really dirty for even considering using it. To be exact, I'd probably be using new Function instead.
Thank you for reading this long thing!