Skip to content

Commit 8d5ad91

Browse files
committed
Add --give-projects-full-access-to-my-computer-including-ability-to-install-malware
1 parent 040fc3c commit 8d5ad91

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
@@ -18,8 +18,6 @@ require('./context-menu');
1818
require('./menu-bar');
1919
require('./crash-messages');
2020

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

157155
app.on('window-all-closed', () => {
158-
if (!isMigrating) {
156+
if (!isInitializing) {
159157
app.quit();
160158
}
161159
});
@@ -174,15 +172,22 @@ app.on('open-file', (event, path) => {
174172
// This event can be called before ready.
175173
if (app.isReady() && !isMigrating) {
176174
// The path we get should already be absolute
177-
EditorWindow.openFiles([path], '');
175+
EditorWindow.openPaths([path], false, false, null);
178176
} else {
179177
filesQueuedToOpen.push(path);
180178
}
181179
});
182180

181+
/**
182+
* @typedef ParsedCommandLine
183+
* @property {string[]} files
184+
* @property {boolean} fullscreen
185+
* @property {boolean} nodeIntegration
186+
*/
187+
183188
/**
184189
* @param {string[]} argv
185-
* @returns {{files: string[]; fullscreen: boolean;}}
190+
* @returns {ParsedCommandLine}
186191
*/
187192
const parseCommandLine = (argv) => {
188193
// argv could be any of:
@@ -201,27 +206,35 @@ const parseCommandLine = (argv) => {
201206
.slice(process.defaultApp ? 2 : 1);
202207

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

205211
return {
206212
files,
207-
fullscreen
213+
fullscreen,
214+
nodeIntegration
208215
};
209216
};
210217

211218
let isMigrating = true;
219+
let isInitializing = true;
212220
let migratePromise = null;
213221

214222
app.on('second-instance', (event, argv, workingDirectory) => {
215223
migratePromise.then(() => {
216224
const commandLineOptions = parseCommandLine(argv);
217-
EditorWindow.openFiles(commandLineOptions.files, commandLineOptions.fullscreen, workingDirectory);
225+
EditorWindow.openPaths(
226+
commandLineOptions.files,
227+
commandLineOptions.fullscreen,
228+
commandLineOptions.nodeIntegration,
229+
workingDirectory
230+
);
218231
});
219232
});
220233

221234
app.whenReady().then(() => {
222235
AbstractWindow.settingsChanged();
223236

224-
migratePromise = migrate().then((shouldContinue) => {
237+
migratePromise = migrate().then(async (shouldContinue) => {
225238
if (!shouldContinue) {
226239
// If we use exit() instead of quit() then openExternal() calls made before the app quits
227240
// won't work on Windows.
@@ -232,10 +245,17 @@ app.whenReady().then(() => {
232245
isMigrating = false;
233246

234247
const commandLineOptions = parseCommandLine(process.argv);
235-
EditorWindow.openFiles([
236-
...filesQueuedToOpen,
237-
...commandLineOptions.files
238-
], commandLineOptions.fullscreen, process.cwd());
248+
await EditorWindow.openPaths(
249+
[
250+
...filesQueuedToOpen,
251+
...commandLineOptions.files
252+
],
253+
commandLineOptions.fullscreen,
254+
commandLineOptions.nodeIntegration,
255+
process.cwd()
256+
);
257+
258+
isInitializing = false;
239259

240260
if (AbstractWindow.getAllWindows().length === 0) {
241261
// 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
@@ -284,19 +288,20 @@ class EditorWindow extends ProjectRunningWindow {
284288
});
285289
});
286290

291+
const titlePrefix = nodeIntegration ? `[${translate('node-integration.prefix')}] ` : '';
287292
this.window.on('page-title-updated', (event, title, explicitSet) => {
288293
event.preventDefault();
289294
if (explicitSet && title) {
290-
this.window.setTitle(`${title} - ${APP_NAME}`);
295+
this.window.setTitle(`${titlePrefix}${title} - ${APP_NAME}`);
291296
this.projectTitle = title;
292297
} else {
293-
this.window.setTitle(APP_NAME);
298+
this.window.setTitle(`${titlePrefix}${APP_NAME}`);
294299
this.projectTitle = '';
295300
}
296301

297302
this.updateRichPresence();
298303
});
299-
this.window.setTitle(APP_NAME);
304+
this.window.setTitle(`${titlePrefix}${APP_NAME}`);
300305

301306
this.window.on('focus', () => {
302307
this.updateRichPresence();
@@ -520,7 +525,7 @@ class EditorWindow extends ProjectRunningWindow {
520525
});
521526

522527
this.ipc.handle('open-new-window', () => {
523-
EditorWindow.newWindow();
528+
EditorWindow.newWindow(null, false, false);
524529
});
525530

526531
this.ipc.handle('open-addon-settings', (event, search) => {
@@ -618,7 +623,7 @@ class EditorWindow extends ProjectRunningWindow {
618623
const projectUrl = params.get('project_url');
619624
const parsedFile = parseOpenedFile(projectUrl, null);
620625
if (parsedFile.type === TYPE_SAMPLE) {
621-
new EditorWindow(parsedFile, null);
626+
EditorWindow.newWindow(parsedFile, false, false);
622627
return {
623628
action: 'deny'
624629
};
@@ -648,26 +653,36 @@ class EditorWindow extends ProjectRunningWindow {
648653
}
649654

650655
/**
651-
* @param {string[]} files
656+
* @param {string[]} paths
652657
* @param {boolean} fullscreen
658+
* @param {boolean} nodeIntegration
653659
* @param {string|null} workingDirectory
660+
* @returns {Promise<void>}
654661
*/
655-
static openFiles (files, fullscreen, workingDirectory) {
656-
if (files.length === 0) {
657-
EditorWindow.newWindow(fullscreen);
658-
} else {
659-
for (const file of files) {
660-
new EditorWindow(parseOpenedFile(file, workingDirectory), fullscreen);
661-
}
662+
static openPaths (paths, fullscreen, nodeIntegration, workingDirectory) {
663+
if (paths.length === 0) {
664+
return EditorWindow.newWindow(null, fullscreen, nodeIntegration);
662665
}
666+
return Promise.all(paths.map(path => (
667+
EditorWindow.newWindow(parseOpenedFile(path, workingDirectory), fullscreen, nodeIntegration)
668+
)));
663669
}
664670

665671
/**
666-
* Open a new window with the default project.
672+
* Try to open a new window.
673+
* @param {OpenedFile|null} file
667674
* @param {boolean} fullscreen
675+
* @param {boolean} nodeIntegration
676+
* @returns {Promise<void>}
668677
*/
669-
static newWindow (fullscreen) {
670-
new EditorWindow(null, fullscreen);
678+
static async newWindow (file, fullscreen, nodeIntegration) {
679+
if (nodeIntegration) {
680+
const allowed = await SecurityPromptWindow.requestNodeIntegration();
681+
if (!allowed) {
682+
return;
683+
}
684+
}
685+
new EditorWindow(file, fullscreen, nodeIntegration);
671686
}
672687
}
673688

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)