codesandbox / codesandbox-client

An online IDE for rapid web development
https://codesandbox.io
Other
13.11k stars 2.29k forks source link

[FR] add iFrame allow to CodeSandbox extension #6188

Open FossPrime opened 3 years ago

FossPrime commented 3 years ago

🌈 Feature

Please make the browser preview behave more like a full browser by removing X-Frame options, which blocks working on Oauth/SAML and other federated features.

https://codesandbox.io/s/builtin-browser-bug-y02x9?file=/src/index.ts

Before Removing X-Frame-options: Screenshot 2021-10-08 11 23 51

There is an extension that already does it here: https://chrome.google.com/webstore/detail/iframe-allow/gifgpciglhhpmeefjdmlpboipkibhbjg

But its security is bit lax. The CSB extension should only do it within the embedded browser preview or only on our Sandboxes.

For linkedIn and youtube you'll might also have to remove the CSP frameblock. Refused to frame 'https://accounts.youtube.com/' because an ancestor violates the following Content Security Policy directive: "frame-ancestors https://accounts.google.com".

After you install an Allow iFrame extension that removes X-Frame-options... which is what I'm proposing the CSB browser extension to do for iFrame document requests made inside the Sandbox browser: image

CompuIves commented 3 years ago

Do we currently send X-Frame options? I don't think we send them, or do you mean something different?

FossPrime commented 3 years ago

https://codesandbox.io/s/builtin-browser-bug-y02x9?file=/src/index.ts

codesandbox.io doesn't... but popular API's often do... like Google/Microsoft/Facebook/LinkedIn's OAuth and SAML API's... as well as Captchas, Credit Card verification, Government ID verification, Online Notarization, and Affiliate link programs. Anything that requires good federation with a sensitive federation that goes as follows:

  1. Navigate away to API partner app
  2. Have API partner run their sensitive app
  3. Navigate back to our app with the payload from the API partner's app

This is something webstorm and VSCode do natively, but we can't do on CodeSandbox.io even if we have the PWA installed. We get elevated privileges to background sync and keyboard shortcuts when we install the PWA, but not the ability to write federated apps in codesandbox.

I've modified the OP with screenshots of part of the problem. I'm using an iFrame... which isn't what you do in Federation, but it is what CodeSandbox.io has to do in practice.

FossPrime commented 3 years ago

Here's the code for similar extensions including one by trailfire that does exactly what I'm proposing... except it whitelists the TrialFire app.

https://chrome.google.com/webstore/detail/trialfire-iframe-enabler/bgcohdnafofkgkaelmkelamidkdgbchh

// listen.js
'use strict';

(function() {
    /**
     * Listen for Trialfire WebExt messages.
     */
    window.addEventListener('message', message => {
        if (message.data && (message.data.topic === 'tfWebExt')) {
            let command = message.data.data;
            switch (command.method) {
                case 'injectMe':
                    commandInjectMe(command);
                    break;
            }
        }
    });

    /**
     * Inject and initialize the tracking-module snippet.
     */
    function commandInjectMe(data) {
        console.log('web-ext is injecting trialfire');
        let script = document.querySelector('[data-tf-api-token]');
        if (script) {
            console.log('trialfire is already in scope');
        } else {
            // Annotate the body with the API token.
            // Tracking-module will automatically initialize with this API token.
            document.body.setAttribute('data-tf-api-token', data.apiToken);

            // Inject the tracking-module snippet.
            script = document.createElement('script');
            script.src = data.assetSrc;
            document.body.appendChild(script);
        }
    }
})();

// trialfire.JS
'use strict';
const config = {
    // Headers we want to strip from the response.
    xssHeaders: new Set([
        'content-security-policy',
        'x-content-security-options',
        'x-frame-options',
        'x-webkit-csp',
        'x-xss-protection'
    ])
};
const state = {
    // The set of tabs IDs that have loaded Trialfire domains.
    tabs: new Set()
};
/**
 * Record the IDs of tabs that load a Trialfire domain.
 */
chrome.tabs.onUpdated.addListener(onTabUpdated);
function onTabUpdated(tabId, changeInfo, tab) {
    if (tab && tab.url) {
        // Determine if this tab has loaded a Trialfire domain.
        let inTrialfire = !!~tab.url.toLowerCase()
            .indexOf('.trialfire.com');
        // Add or remove this tab from the set of Trialfire tabs.
        let knownTab = state.tabs.has(tabId);
        if (!knownTab && inTrialfire) {
            // Tab has loaded a Trialfire domain.
            state.tabs.add(tabId);
        } else
        if (knownTab && !inTrialfire) {
            // Tab has unloaded a Trialfire domain.
            state.tabs.delete(tabId);
        }
    }
}
/**
 * Strip XSS headers from tabs that have loaded Trialfire domains.
 */
chrome.webRequest.onHeadersReceived.addListener(stripXSSHeaders, {
        urls: ['<all_urls>']
    },
    // Needs to be blocking so headers can be modified.
    ['blocking', 'responseHeaders', 'extraHeaders']);
function stripXSSHeaders(details) {
    let headers;
    if (state.tabs.has(details.tabId)) {
        headers = details.responseHeaders.filter(header => !config.xssHeaders.has(header.name.toLowerCase()));
    } else {
        headers = details.responseHeaders;
    }
    return {
        responseHeaders: headers
    };
}
/**
 * Listen for DOMContentLoaded events in Trialfire tabs.
 */
chrome.webNavigation.onDOMContentLoaded.addListener(onDOMContentLoaded);
function onDOMContentLoaded(details) {
    // Frame ID must be non-zero to indicate nested iframe.
    if (state.tabs.has(details.tabId) && (details.frameId > 0)) {
        if (details.url.toLowerCase()
            .startsWith('http')) {
            chrome.tabs.executeScript(details.tabId, {
                frameId: details.frameId,
                file: '/listen.js',
                runAt: 'document_start'
            });
        }
    }
}

iFrame Allow

chrome.webRequest.onHeadersReceived.addListener(
    function(details) {
        for (var j = 0; j < details.responseHeaders.length; ++j) {
            if (details.responseHeaders[j].name.toLowerCase() == 'x-frame-options') {
                details.responseHeaders.splice(j, 1);
                return {
                    responseHeaders: details.responseHeaders
                };
            }
        }
    }, {
        urls: ["<all_urls>"]
    }, ["blocking", "responseHeaders"]);