diff --git a/src-main/windows/abstract.js b/src-main/windows/abstract.js index 38779dd6..bee12f91 100644 --- a/src-main/windows/abstract.js +++ b/src-main/windows/abstract.js @@ -41,6 +41,7 @@ class AbstractWindow { } this.initialURL = null; + this.protocol = null; const cls = this.constructor; if (!windowsByClass.has(cls)) { @@ -194,6 +195,7 @@ class AbstractWindow { loadURL (url) { this.initialURL = url; + this.protocol = new URL(url).protocol; return this.window.loadURL(url); } diff --git a/src-main/windows/packager.js b/src-main/windows/packager.js index 491aac85..9d0fcf12 100644 --- a/src-main/windows/packager.js +++ b/src-main/windows/packager.js @@ -52,7 +52,8 @@ class PackagerWindow extends AbstractWindow { }); this.window.webContents.on('did-create-window', (newWindow) => { - new PackagerPreviewWindow(this.window, newWindow); + const childWindow = new PackagerPreviewWindow(this.window, newWindow); + childWindow.protocol = this.protocol; }); this.loadURL('tw-packager://./standalone.html'); diff --git a/src-main/windows/project-running-window.js b/src-main/windows/project-running-window.js index 46372c3e..6ca9d0a3 100644 --- a/src-main/windows/project-running-window.js +++ b/src-main/windows/project-running-window.js @@ -125,15 +125,36 @@ class ProjectRunningWindow extends AbtractWindow { // Headers from Electron are not normalized, so we have to make sure to remove uppercased // variations on our own. const normalized = key.toLowerCase(); - if (normalized === 'access-control-allow-origin' || normalized === 'x-frame-options') { + + if ( + // Above we forced this header to be *, so ignore any other value + normalized === 'access-control-allow-origin' || + // Remove x-frame-options so that embedding is allowed + normalized === 'x-frame-options' + ) { // Ignore header. - } else if (normalized === 'content-security-policy') { - // Remove frame-ancestors header while preserving the rest of the CSP. - // frame-ancestors does not fall back to default-src so we just need to remove, not overwrite. - // Regex based on ABNF from https://www.w3.org/TR/CSP3/#grammardef-serialized-policy - newHeaders[key] = details.responseHeaders[key].map(csp => ( - csp.replace(/(?:;[\x09\x0A\x0C\x0D\x20]*)?frame-ancestors[\x09\x0A\x0C\x0D\x20]+[^;,]+/ig, '') - )); + } else if ( + normalized === 'content-security-policy' || + // We include the report-only header so that we send fewer useless reports + normalized === 'content-security-policy-report-only' + ) { + // Seems to be an Electron quirk where CSP with custom protocols can't do specific + // origins, only an entire protocol. We use a lot of separate protocols so that's fine. + // It is possible for protocol to be null, so handle that. + const extraSources = this.protocol ? this.protocol : null; + + if (extraSources) { + // If the page has a frame-ancestors directive, add ourselves to it so that we can embed + // the page without compromising security more than absolutely necessary. + // frame-ancestors does not fall back to default-src so we don't need to worry about that. + // Regex based on ABNF from https://www.w3.org/TR/CSP3/#grammardef-serialized-policy + newHeaders[key] = details.responseHeaders[key].map(csp => ( + csp.replace( + /((?:;[\x09\x0A\x0C\x0D\x20]*)?frame-ancestors[\x09\x0A\x0C\x0D\x20]+)([^;,]+)/ig, + (_, directiveName, directiveValue) => `${directiveName}${directiveValue} ${extraSources}` + ) + )); + } } else { newHeaders[key] = details.responseHeaders[key]; }