Skip to content

Commit 9cccadd

Browse files
committed
Add --give-projects-full-access-to-my-computer-including-ability-to-install-malware
1 parent 54261d8 commit 9cccadd

File tree

7 files changed

+126
-46
lines changed

7 files changed

+126
-46
lines changed

src-main/index.js

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@ require('./context-menu');
1717
require('./menu-bar');
1818
require('./crash-messages');
1919

20-
app.enableSandbox();
21-
2220
// Allows certain versions of Scratch Link to work without an internet connection
2321
// https://github.com/LLK/scratch-desktop/blob/4b462212a8e406b15bcf549f8523645602b46064/src/main/index.js#L45
2422
app.commandLine.appendSwitch('host-resolver-rules', 'MAP device-manager.scratch.mit.edu 127.0.0.1');
@@ -130,7 +128,7 @@ app.on('web-contents-created', (event, webContents) => {
130128
});
131129

132130
app.on('window-all-closed', () => {
133-
if (!isMigrating) {
131+
if (!isInitializing) {
134132
app.quit();
135133
}
136134
});
@@ -149,15 +147,22 @@ app.on('open-file', (event, path) => {
149147
// This event can be called before ready.
150148
if (app.isReady() && !isMigrating) {
151149
// The path we get should already be absolute
152-
EditorWindow.openFiles([path], '');
150+
EditorWindow.openPaths([path], false, false, null);
153151
} else {
154152
filesQueuedToOpen.push(path);
155153
}
156154
});
157155

156+
/**
157+
* @typedef ParsedCommandLine
158+
* @property {string[]} files
159+
* @property {boolean} fullscreen
160+
* @property {boolean} nodeIntegration
161+
*/
162+
158163
/**
159164
* @param {string[]} argv
160-
* @returns {{files: string[]; fullscreen: boolean;}}
165+
* @returns {ParsedCommandLine}
161166
*/
162167
const parseCommandLine = (argv) => {
163168
// argv could be any of:
@@ -176,27 +181,35 @@ const parseCommandLine = (argv) => {
176181
.slice(process.defaultApp ? 2 : 1);
177182

178183
const fullscreen = argv.includes('--fullscreen');
184+
const nodeIntegration = argv.includes('--give-projects-full-access-to-my-computer-including-ability-to-install-malware');
179185

180186
return {
181187
files,
182-
fullscreen
188+
fullscreen,
189+
nodeIntegration
183190
};
184191
};
185192

186193
let isMigrating = true;
194+
let isInitializing = true;
187195
let migratePromise = null;
188196

189197
app.on('second-instance', (event, argv, workingDirectory) => {
190198
migratePromise.then(() => {
191199
const commandLineOptions = parseCommandLine(argv);
192-
EditorWindow.openFiles(commandLineOptions.files, commandLineOptions.fullscreen, workingDirectory);
200+
EditorWindow.openPaths(
201+
commandLineOptions.files,
202+
commandLineOptions.fullscreen,
203+
commandLineOptions.nodeIntegration,
204+
workingDirectory
205+
);
193206
});
194207
});
195208

196209
app.whenReady().then(() => {
197210
AbstractWindow.settingsChanged();
198211

199-
migratePromise = migrate().then((shouldContinue) => {
212+
migratePromise = migrate().then(async (shouldContinue) => {
200213
if (!shouldContinue) {
201214
// If we use exit() instead of quit() then openExternal() calls made before the app quits
202215
// won't work on Windows.
@@ -207,10 +220,17 @@ app.whenReady().then(() => {
207220
isMigrating = false;
208221

209222
const commandLineOptions = parseCommandLine(process.argv);
210-
EditorWindow.openFiles([
211-
...filesQueuedToOpen,
212-
...commandLineOptions.files
213-
], commandLineOptions.fullscreen, process.cwd());
223+
await EditorWindow.openPaths(
224+
[
225+
...filesQueuedToOpen,
226+
...commandLineOptions.files
227+
],
228+
commandLineOptions.fullscreen,
229+
commandLineOptions.nodeIntegration,
230+
process.cwd()
231+
);
232+
233+
isInitializing = false;
214234

215235
if (AbstractWindow.getAllWindows().length === 0) {
216236
// No windows were successfully opened. Let's just quit.

src-main/l10n/en.json

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -424,36 +424,48 @@
424424
"developer_comment": "Appears in file saving dialog when saving some types of packaged projects"
425425
},
426426
"security-prompt.title": {
427-
"string": "Extension Security",
428-
"developer_comment": "Title of extension security prompt window"
427+
"string": "Security",
428+
"developer_comment": "Title of desktop app's security prompt window"
429429
},
430430
"security-prompt.allow": {
431431
"string": "Allow",
432-
"developer_comment": "Title of extension security prompt window. This button allows the permission."
432+
"developer_comment": "Title of desktop app's security prompt window. This button grants the request."
433433
},
434434
"security-prompt.deny": {
435435
"string": "Deny",
436-
"developer_comment": "Title of extension security prompt window. This button denies the permission."
436+
"developer_comment": "Title of desktop app's security prompt window. This button denies the request."
437+
},
438+
"security-prompt.node-integration1": {
439+
"string": "{APP_NAME} was started in a special mode that will give any opened project unrestricted access to your computer. This would allow stealing passwords and installing malware.",
440+
"developer_comment": "Part of the desktop app's security prompt window"
441+
},
442+
"security-prompt.node-integration2": {
443+
"string": "The {APP_NAME} developers are not responsible for any damages.",
444+
"developer_comment": "Part of the desktop app's security prompt window"
445+
},
446+
"security-prompt.node-integration3": {
447+
"string": "If you were not expecting to see this screen, please click \"Deny\" to safely exit.",
448+
"developer_comment": "Part of the desktop app's security prompt window"
437449
},
438450
"security-prompt.read-clipboard1": {
439451
"string": "The project wants to read data from your clipboard.",
440-
"developer_comment": "Part of the extension security prompt window"
452+
"developer_comment": "Part of the desktop app's security prompt window"
441453
},
442454
"security-prompt.read-clipboard2": {
443455
"string": "If your clipboard contains things like passwords, the project may be able to share those with other users or servers.",
444-
"developer_comment": "Part of the extension security prompt window"
456+
"developer_comment": "Part of the desktop app's security prompt window"
445457
},
446458
"security-prompt.read-clipboard3": {
447459
"string": "Clipboard access may not work on some systems. If allowed, further clipboard reads will be automatically allowed.",
448-
"developer_comment": "Part of the extension security prompt window"
460+
"developer_comment": "Part of the desktop app's security prompt window"
449461
},
450462
"security-prompt.notifications1": {
451463
"string": "The project wants to display notifications.",
452-
"developer_comment": "Part of the extension security prompt window"
464+
"developer_comment": "Part of the desktop app's security prompt window"
453465
},
454466
"security-prompt.notifications2": {
455467
"string": "If allowed, you may be prompted to enable notifications by your operating system, and further notifications will be automatically allowed.",
456-
"developer_comment": "Part of the extension security prompt window"
468+
"developer_comment": "Part of the desktop app's security prompt window"
457469
},
458470
"unsafe-path.title": {
459471
"string": "Invalid File Location",
@@ -486,5 +498,9 @@
486498
"extension-documentation.title": {
487499
"string": "{APP_NAME} Extension Documentation",
488500
"developer_comment": "Title of in-app window for viewing extension documentation."
501+
},
502+
"node-integration.prefix": {
503+
"string": "Unrestricted System Access",
504+
"developer_comment": "Appears in titles of desktop app windows that have been given full access to the user's sytem"
489505
}
490506
}

src-main/windows/abstract.js

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const windowsByClass = new Map();
1010
* @typedef AbstractWindowOptions
1111
* @property {Electron.BrowserWindow} [existingWindow]
1212
* @property {Electron.BrowserWindow} [parentWindow]
13+
* @property {boolean} [nodeIntegration]
1314
*/
1415

1516
class AbstractWindow {
@@ -18,7 +19,7 @@ class AbstractWindow {
1819
this.parentWindow = options.parentWindow || null;
1920

2021
/** @type {Electron.BrowserWindow} */
21-
this.window = options.existingWindow || new BrowserWindow(this.getWindowOptions());
22+
this.window = options.existingWindow || new BrowserWindow(this.getWindowOptions(options));
2223
this.window.webContents.on('before-input-event', this.handleInput.bind(this));
2324
this.applySettings();
2425

@@ -163,7 +164,10 @@ class AbstractWindow {
163164
return '#ffffff';
164165
}
165166

166-
getWindowOptions () {
167+
/**
168+
* @param {AbstractWindowOptions} constructorOptions
169+
*/
170+
getWindowOptions (constructorOptions) {
167171
/** @type {Electron.BrowserWindowConstructorOptions} */
168172
const options = {};
169173

@@ -174,8 +178,11 @@ class AbstractWindow {
174178
// Child classes are expected to show the window on their own
175179
options.show = false;
176180

177-
// These should all be redundant already, but defense-in-depth.
178-
options.webPreferences = {
181+
options.webPreferences = constructorOptions.nodeIntegration ? {
182+
nodeIntegration: true,
183+
contextIsolation: false,
184+
sandbox: false,
185+
} : {
179186
nodeIntegration: false,
180187
contextIsolation: true,
181188
sandbox: true,

src-main/windows/editor.js

Lines changed: 33 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ const privilegedFetch = require('../fetch');
1919
const RichPresence = require('../rich-presence.js');
2020
const FileAccessWindow = require('./file-access-window.js');
2121
const ExtensionDocumentationWindow = require('./extension-documentation.js');
22+
const SecurityPromptWindow = require('./security-prompt.js');
2223

2324
const TYPE_FILE = 'file';
2425
const TYPE_URL = 'url';
@@ -215,9 +216,12 @@ class EditorWindow extends ProjectRunningWindow {
215216
/**
216217
* @param {OpenedFile|null} initialFile
217218
* @param {boolean} isInitiallyFullscreen
219+
* @param {boolean} nodeIntegration
218220
*/
219-
constructor (initialFile, isInitiallyFullscreen) {
220-
super();
221+
constructor (initialFile, isInitiallyFullscreen, nodeIntegration) {
222+
super({
223+
nodeIntegration
224+
});
221225

222226
/**
223227
* Ideally we would revoke access after loading a new project, but our file handle handling in
@@ -267,19 +271,20 @@ class EditorWindow extends ProjectRunningWindow {
267271
}
268272
});
269273

274+
const titlePrefix = nodeIntegration ? `[${translate('node-integration.prefix')}] ` : '';
270275
this.window.on('page-title-updated', (event, title, explicitSet) => {
271276
event.preventDefault();
272277
if (explicitSet && title) {
273-
this.window.setTitle(`${title} - ${APP_NAME}`);
278+
this.window.setTitle(`${titlePrefix}${title} - ${APP_NAME}`);
274279
this.projectTitle = title;
275280
} else {
276-
this.window.setTitle(APP_NAME);
281+
this.window.setTitle(`${titlePrefix}${APP_NAME}`);
277282
this.projectTitle = '';
278283
}
279284

280285
this.updateRichPresence();
281286
});
282-
this.window.setTitle(APP_NAME);
287+
this.window.setTitle(`${titlePrefix}${APP_NAME}`);
283288

284289
this.window.on('focus', () => {
285290
this.updateRichPresence();
@@ -504,7 +509,7 @@ class EditorWindow extends ProjectRunningWindow {
504509
});
505510

506511
this.ipc.handle('open-new-window', () => {
507-
EditorWindow.newWindow();
512+
EditorWindow.newWindow(null, false, false);
508513
});
509514

510515
this.ipc.handle('open-addon-settings', (event, search) => {
@@ -602,7 +607,7 @@ class EditorWindow extends ProjectRunningWindow {
602607
const projectUrl = params.get('project_url');
603608
const parsedFile = parseOpenedFile(projectUrl, null);
604609
if (parsedFile.type === TYPE_SAMPLE) {
605-
new EditorWindow(parsedFile, null);
610+
EditorWindow.newWindow(parsedFile, false, false);
606611
return {
607612
action: 'deny'
608613
};
@@ -632,26 +637,36 @@ class EditorWindow extends ProjectRunningWindow {
632637
}
633638

634639
/**
635-
* @param {string[]} files
640+
* @param {string[]} paths
636641
* @param {boolean} fullscreen
642+
* @param {boolean} nodeIntegration
637643
* @param {string|null} workingDirectory
644+
* @returns {Promise<void>}
638645
*/
639-
static openFiles (files, fullscreen, workingDirectory) {
640-
if (files.length === 0) {
641-
EditorWindow.newWindow(fullscreen);
642-
} else {
643-
for (const file of files) {
644-
new EditorWindow(parseOpenedFile(file, workingDirectory), fullscreen);
645-
}
646+
static openPaths (paths, fullscreen, nodeIntegration, workingDirectory) {
647+
if (paths.length === 0) {
648+
return EditorWindow.newWindow(null, fullscreen, nodeIntegration);
646649
}
650+
return Promise.all(paths.map(path => (
651+
EditorWindow.newWindow(parseOpenedFile(path, workingDirectory), fullscreen, nodeIntegration)
652+
)));
647653
}
648654

649655
/**
650-
* Open a new window with the default project.
656+
* Try to open a new window.
657+
* @param {OpenedFile|null} file
651658
* @param {boolean} fullscreen
659+
* @param {boolean} nodeIntegration
660+
* @returns {Promise<void>}
652661
*/
653-
static newWindow (fullscreen) {
654-
new EditorWindow(null, fullscreen);
662+
static async newWindow (file, fullscreen, nodeIntegration) {
663+
if (nodeIntegration) {
664+
const allowed = await SecurityPromptWindow.requestNodeIntegration();
665+
if (!allowed) {
666+
return;
667+
}
668+
}
669+
new EditorWindow(file, fullscreen, nodeIntegration);
655670
}
656671
}
657672

src-main/windows/security-prompt.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,14 +62,16 @@ class SecurityState {
6262

6363
class SecurityPromptWindow extends AbstractWindow {
6464
/**
65-
* @param {Electron.BrowserWindow} projectWindow
65+
* @param {Electron.BrowserWindow|null} projectWindow
6666
* @param {string} type
6767
*/
6868
constructor (projectWindow, type) {
6969
super({
7070
parentWindow: projectWindow
7171
});
7272

73+
this.type = type;
74+
7375
/** @type {Promise<boolean>} */
7476
this.promptPromise = new Promise((resolve) => {
7577
this.promptResolve = resolve;
@@ -130,6 +132,13 @@ class SecurityPromptWindow extends AbstractWindow {
130132
return this.promptPromise;
131133
}
132134

135+
static async requestNodeIntegration () {
136+
const securityWindows = AbstractWindow.getWindowsByClass(SecurityPromptWindow);
137+
const existingWindow = securityWindows.find(i => i.type === 'node-integration');
138+
const window = existingWindow || new SecurityPromptWindow(null, 'node-integration');
139+
return window.done();
140+
}
141+
133142
static async requestReadClipboard (window) {
134143
const state = SecurityState.forWindow(window);
135144
if (!state.allowedReadClipboard) {

src-preload/editor.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
const {contextBridge, ipcRenderer} = require('electron');
22

3-
contextBridge.exposeInMainWorld('EditorPreload', {
3+
const exposeInMainWorld = (name, api) => {
4+
// TODO: find a better way to do this
5+
try {
6+
contextBridge.exposeInMainWorld(name, api);
7+
} catch (e) {
8+
global[name] = api;
9+
}
10+
};
11+
12+
exposeInMainWorld('EditorPreload', {
413
isInitiallyFullscreen: () => ipcRenderer.sendSync('is-initially-fullscreen'),
514
getInitialFile: () => ipcRenderer.invoke('get-initial-file'),
615
getFile: (id) => ipcRenderer.invoke('get-file', id),
@@ -66,7 +75,7 @@ ipcRenderer.on('enumerate-media-devices', (e) => {
6675
});
6776
});
6877

69-
contextBridge.exposeInMainWorld('PromptsPreload', {
78+
exposeInMainWorld('PromptsPreload', {
7079
alert: (message) => ipcRenderer.sendSync('alert', message),
7180
confirm: (message) => ipcRenderer.sendSync('confirm', message),
7281
});

src-renderer/security-prompt/security-prompt.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@
5959

6060
<body>
6161
<main>
62+
<p data-type="node-integration" data-l10n="security-prompt.node-integration1"></p>
63+
<p data-type="node-integration" data-l10n="security-prompt.node-integration2"></p>
64+
<p data-type="node-integration" data-l10n="security-prompt.node-integration3"></p>
65+
6266
<p data-type="read-clipboard" data-l10n="security-prompt.read-clipboard1"></p>
6367
<p data-type="read-clipboard" data-l10n="security-prompt.read-clipboard2"></p>
6468
<p data-type="read-clipboard" data-l10n="security-prompt.read-clipboard3"></p>

0 commit comments

Comments
 (0)