Skip to content

Commit 4b85b14

Browse files
committed
chore: tighten up REPL security
- don't allow top level navigations without user interaction (prevents stuff like an eager `window.load()`) - don't allow links to other pages to escape sandbox restrictions (as a result most links don't work) if you're visiting a hashed REPL (we can't remove malicious code from them since code is encoded into the URL)
1 parent a51a014 commit 4b85b14

File tree

6 files changed

+17
-23
lines changed

6 files changed

+17
-23
lines changed

apps/svelte.dev/src/routes/(authed)/playground/[id]/+page.svelte

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import { mapbox_setup } from '../../../../config.js';
99
import AppControls from './AppControls.svelte';
1010
import { compress_and_encode_text, decode_and_decompress_text } from './gzip.js';
11+
import { page } from '$app/stores';
1112
1213
let { data } = $props();
1314
@@ -18,6 +19,10 @@
1819
let version = data.version;
1920
let setting_hash: any = null;
2021
22+
// Hashed URLs are less safe (we can't delete malicious REPLs), therefore
23+
// don't allow links to escape the sandbox restrictions
24+
const can_escape = browser && !$page.url.hash;
25+
2126
onMount(() => {
2227
if (version !== 'local') {
2328
fetch(`https://unpkg.com/svelte@${version}/package.json`)
@@ -127,6 +132,7 @@
127132
bind:this={repl}
128133
{svelteUrl}
129134
{relaxed}
135+
{can_escape}
130136
vim={data.vim}
131137
injectedJS={mapbox_setup}
132138
showModified

apps/svelte.dev/src/routes/(authed)/playground/[id]/embed/+page.svelte

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
bind:this={repl}
5151
{svelteUrl}
5252
{relaxed}
53+
can_escape
5354
injectedJS={mapbox_setup}
5455
showModified
5556
showAst

apps/svelte.dev/src/routes/tutorial/[slug]/OutputRollup.svelte

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
{#if browser}
3535
<Viewer
3636
relaxed
37+
can_escape
3738
onLog={(l: Log[]) => logs = l}
3839
{bundle}
3940
theme={$theme.current}

packages/repl/src/lib/Output/Output.svelte

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
export let runtimeError: Error | null = null;
1414
export let embedded = false;
1515
export let relaxed = false;
16+
export let can_escape = false;
1617
export let injectedJS: string;
1718
export let injectedCSS: string;
1819
export let showAst = false;
@@ -61,6 +62,7 @@
6162
bind:error={runtimeError}
6263
{status}
6364
{relaxed}
65+
{can_escape}
6466
{injectedJS}
6567
{injectedCSS}
6668
theme={previewTheme}

packages/repl/src/lib/Output/Viewer.svelte

Lines changed: 5 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
export let status: string | null;
1919
/** sandbox allow-same-origin */
2020
export let relaxed = false;
21+
/** sandbox allow-popups-to-escape-sandbox (i.e. links within the REPL to other pages work) */
22+
export let can_escape = false;
2123
/** Any additional JS you may want to inject */
2224
export let injectedJS = '';
2325
/** Any additional CSS you may want to inject */
@@ -247,13 +249,13 @@
247249
class:inited
248250
bind:this={iframe}
249251
sandbox={[
250-
'allow-popups-to-escape-sandbox',
251252
'allow-scripts',
252253
'allow-popups',
253254
'allow-forms',
254255
'allow-pointer-lock',
255-
'allow-top-navigation',
256+
'allow-top-navigation-by-user-activation',
256257
'allow-modals',
258+
can_escape ? 'allow-popups-to-escape-sandbox' : '',
257259
relaxed ? 'allow-same-origin' : ''
258260
].join(' ')}
259261
class={error || pending || pending_imports ? 'greyed-out' : ''}
@@ -269,27 +271,7 @@
269271
{#if !onLog}
270272
<PaneWithPanel pos="90%" panel="Console">
271273
<div slot="main">
272-
<iframe
273-
title="Result"
274-
class:inited
275-
bind:this={iframe}
276-
sandbox={[
277-
'allow-popups-to-escape-sandbox',
278-
'allow-scripts',
279-
'allow-popups',
280-
'allow-forms',
281-
'allow-pointer-lock',
282-
'allow-top-navigation',
283-
'allow-modals',
284-
relaxed ? 'allow-same-origin' : ''
285-
].join(' ')}
286-
class={error || pending || pending_imports ? 'greyed-out' : ''}
287-
srcdoc={BROWSER ? srcdoc : ''}
288-
></iframe>
289-
290-
{#if $bundle?.error}
291-
<ErrorOverlay error={$bundle.error} />
292-
{/if}
274+
{@render main()}
293275
</div>
294276

295277
<div slot="panel-header">

packages/repl/src/lib/Repl.svelte

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
export let embedded = false;
2121
export let orientation: 'columns' | 'rows' = 'columns';
2222
export let relaxed = false;
23+
export let can_escape = false;
2324
export let fixed = false;
2425
export let fixedPos = 50;
2526
export let injectedJS = '';
@@ -312,6 +313,7 @@
312313
status={status_visible ? status : null}
313314
{embedded}
314315
{relaxed}
316+
{can_escape}
315317
{injectedJS}
316318
{injectedCSS}
317319
{showAst}

0 commit comments

Comments
 (0)