Skip to content

Commit 2aca02e

Browse files
committed
Allow Extension URLs to load unsandboxed
This may sound like a really bad change to make, however I have some reasons as to why I believe this is perfectly fine: 1. Why is it fine to load text unsandboxed? There's no difference between a URL extension loading unsandboxed and a text extension using eval to load all of it's code from another website. 2. Sharing & updating convenience If I made an extension and already had it hosting perfectly fine, I would rather share the link to that hosting than tell everyone to download it and keep coming back to the website incase there is an update. Again, malicious updates would still be possible if a file-loaded extension just used eval & fetch to run all of it's code.
1 parent 3f90d96 commit 2aca02e

File tree

7 files changed

+50
-33
lines changed

7 files changed

+50
-33
lines changed

src/components/custom-procedures/custom-procedures.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import Modal from '../../containers/modal.jsx';
44
import Box from '../box/box.jsx';
55
import { defineMessages, injectIntl, intlShape, FormattedMessage } from 'react-intl';
66

7-
import plusIcon from './icon--plus.svg';
7+
import dropperIcon from './icon--dropper.svg';
88

99
import booleanInputIcon from './icon--boolean-input.svg';
1010
import textInputIcon from './icon--text-input.svg';
@@ -153,7 +153,7 @@ const BlockColorSection = props => (
153153
onChange={props.onBlockColorChange}
154154
/>
155155
<img
156-
src={plusIcon}
156+
src={dropperIcon}
157157
className={styles.customPlus}
158158
/>
159159
</div>
Lines changed: 8 additions & 0 deletions
Loading

src/components/custom-procedures/icon--plus.svg

Lines changed: 0 additions & 5 deletions
This file was deleted.

src/components/tw-custom-extension-modal/custom-extension-modal.jsx

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -181,17 +181,20 @@ const CustomExtensionModal = props => (
181181
/>
182182
</p>
183183
)}
184-
<p>
185-
<FormattedMessage
186-
// eslint-disable-next-line max-len
187-
defaultMessage="Your browser may not allow PenguinMod to access certain sites. If this is causing issues for you, try loading from a file or text instead."
188-
description="Message that appears in custom extension prompt"
189-
id="pm.customExtensionModal.corsProblem"
190-
/>
191-
</p>
192184
</React.Fragment>
193185
)}
194186

187+
{props.type === 'url' && (
188+
<p>
189+
<FormattedMessage
190+
// eslint-disable-next-line max-len
191+
defaultMessage="Your browser may not allow PenguinMod to access certain sites. If this is causing issues for you, try loading from a file or text instead."
192+
description="Message that appears in custom extension prompt"
193+
id="pm.customExtensionModal.corsProblem"
194+
/>
195+
</p>
196+
)}
197+
195198
<label className={styles.checkboxContainer}>
196199
<FancyCheckbox
197200
className={styles.basicCheckbox}

src/containers/extension-library.jsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ class ExtensionLibrary extends React.PureComponent {
135135
}
136136
}
137137

138-
handleItemSelect(item) {
138+
async handleItemSelect(item) {
139139
// eslint-disable-next-line no-alert
140140
// if (item.incompatibleWithScratch && !confirm(this.props.intl.formatMessage(messages.incompatible))) {
141141
// return;
@@ -158,8 +158,12 @@ class ExtensionLibrary extends React.PureComponent {
158158
return;
159159
}
160160
const url = (item.extensionURL ? item.extensionURL : extensionId);
161-
if (item._unsandboxed && url.startsWith("data:")) {
162-
manuallyTrustExtension(url);
161+
if (item._unsandboxed) {
162+
if (url.startsWith("data:")) {
163+
manuallyTrustExtension(url);
164+
} else {
165+
await this.props.vm.securityManager.canLoadExtensionFromProject(url);
166+
}
163167
}
164168
if (!item.disabled) {
165169
if (this.props.vm.extensionManager.isExtensionLoaded(extensionId)) {

src/containers/tw-custom-extension-modal.jsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import log from '../lib/log';
66
import localforage from 'localforage';
77
import CustomExtensionModalComponent from '../components/tw-custom-extension-modal/custom-extension-modal.jsx';
88
import {closeCustomExtensionModal} from '../reducers/modals';
9-
import {manuallyTrustExtension, isTrustedExtension} from './tw-security-manager.jsx';
9+
import {manuallyTrustExtension, isTrustedExtension, isTrustedExtensionOrigin} from './tw-security-manager.jsx';
1010

1111
const generateRandomId = () => {
1212
const randomChars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
@@ -125,7 +125,7 @@ class CustomExtensionModal extends React.Component {
125125
this.handleClose();
126126
try {
127127
const url = await this.getExtensionURL();
128-
if (this.state.type !== 'url' && this.state.unsandboxed) {
128+
if (this.state.unsandboxed) {
129129
manuallyTrustExtension(url);
130130
}
131131
if (this.props.swapId) {
@@ -220,12 +220,15 @@ class CustomExtensionModal extends React.Component {
220220
}
221221
isUnsandboxed () {
222222
if (this.state.type === 'url') {
223-
return isTrustedExtension(this.state.url);
223+
if (isTrustedExtensionOrigin(this.state.url)) return true;
224224
}
225225
return this.state.unsandboxed;
226226
}
227227
canChangeUnsandboxed () {
228-
return this.state.type !== 'url';
228+
if (this.state.type === "url" && isTrustedExtensionOrigin(this.state.url)) {
229+
return false;
230+
}
231+
return true;
229232
}
230233
handleChangeUnsandboxed (e) {
231234
this.setState({

src/containers/tw-security-manager.jsx

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@ const manuallyTrustExtension = url => {
1616
};
1717

1818
/**
19-
* Trusted extensions are loaded automatically and without a sandbox.
19+
* Trusted URL origins are allowed to load extensions without a sandbox automatically.
2020
* @param {string} url URL as a string.
2121
* @returns {boolean} True if the extension can is trusted
2222
*/
23-
const isTrustedExtension = url => (
23+
const isTrustedExtensionOrigin = url => (
2424
// Always trust our official extension repostiory.
2525
url.startsWith('https://extensions.turbowarp.org/') ||
2626
url.startsWith('https://extensions.penguinmod.com/') ||
@@ -37,8 +37,14 @@ const isTrustedExtension = url => (
3737
url.startsWith('http://localhost:5173') || // Local Home or Extensions
3838
url.startsWith('http://localhost:5174') || // Local Home or Extensions
3939

40-
extensionsTrustedByUser.has(url)
40+
false // ignore this, just makes copy & paste easier
4141
);
42+
/**
43+
* Trusted extensions are loaded automatically and without a sandbox.
44+
* @param {string} url URL as a string.
45+
* @returns {boolean} True if the extension can is trusted
46+
*/
47+
const isTrustedExtension = url => (isTrustedExtensionOrigin(url) || extensionsTrustedByUser.has(url));
4248

4349
/**
4450
* Set of fetch resource origins that were manually trusted by the user.
@@ -284,8 +290,10 @@ class TWSecurityManagerComponent extends React.Component {
284290
return true;
285291
}
286292
const { showModal } = await this.acquireModalLock();
287-
// for backwards compatibility, allow urls to be unsandboxed
288-
// if (url.startsWith('data:')) {
293+
294+
// we allow all urls to be unsandboxed.
295+
// its very likely that people would load any file unsandboxed anyways, theres no safety in blocking it for urls only.
296+
// when a file is unsandboxed it can request any website anyways, so its not like its preventing remote updates either.
289297
const allowed = await showModal(SecurityModals.LoadExtension, {
290298
url,
291299
unsandboxed: true,
@@ -304,11 +312,6 @@ class TWSecurityManagerComponent extends React.Component {
304312
};
305313
}
306314
return allowed;
307-
// }
308-
// return showModal(SecurityModals.LoadExtension, {
309-
// url,
310-
// unsandboxed: false
311-
// });
312315
}
313316

314317
/**
@@ -502,5 +505,6 @@ const ConnectedSecurityManagerComponent = connect(
502505
export {
503506
ConnectedSecurityManagerComponent as default,
504507
manuallyTrustExtension,
505-
isTrustedExtension
508+
isTrustedExtension,
509+
isTrustedExtensionOrigin
506510
};

0 commit comments

Comments
 (0)