Getting around X-Frame-Options DENY in a Chrome extension?

JavascriptIframeGoogle Chrome-ExtensionX Frame-Options

Javascript Problem Overview


I'm the author of Intab, a Chrome extension that lets you view a link inline as opposed to a new tab. There's not much fancy stuff going on behind the scenes, it's just an iframe that loads the URL the user clicked on.

It works great except for sites that set the X-Frame-Options header to DENY or SAMEORIGIN. Some really big sites like Google and Facebook both use it which makes for a slightly janky experience.

Is there any way to get around this? Since I'm using a Chrome extension, is there any browser level stuff I can access that might help? Looking for any ideas or help!

Javascript Solutions


Solution 1 - Javascript

Chrome offers the webRequest API to intercept and modify HTTP requests. You can remove the X-Frame-Options header to allow inlining pages within an iframe.

chrome.webRequest.onHeadersReceived.addListener(
    function(info) {
        var headers = info.responseHeaders;
        for (var i=headers.length-1; i>=0; --i) {
            var header = headers[i].name.toLowerCase();
            if (header == 'x-frame-options' || header == 'frame-options') {
                headers.splice(i, 1); // Remove header
            }
        }
        return {responseHeaders: headers};
    }, {
        urls: [
            '*://*/*', // Pattern to match all http(s) pages
            // '*://*.example.org/*', // Pattern to match one http(s) site
        ], 
        types: [ 'sub_frame' ]
    }, [        'blocking',        'responseHeaders',        // Modern Chrome needs 'extraHeaders' to see and change this header,        // so the following code evaluates to 'extraHeaders' only in modern Chrome.        chrome.webRequest.OnHeadersReceivedOptions.EXTRA_HEADERS,    ].filter(Boolean)
);

In the manifest, you need to specify the webRequest and webRequestBlocking permissions, plus the URLs patterns you're intending to intercept i.e. "*://*/*" or "*://www.example.org/*" for the example above.

Solution 2 - Javascript

ManifestV3 example using declarativeNetRequest

Let's use the new declarativeNetRequest API to strip the header only when the iframe is embedded in pages of our extension.

manifest.json:

  "permissions": ["declarativeNetRequest"],
  "host_permissions": ["*://*.example.com/"],
  "background": {"service_worker": "bg.js"},

The declarativeNetRequest permission adds an entry in the installation dialog that your extension can "Block page content" so to avoid it you can use declarativeNetRequestWithHostAccess:

manifest.json for Chrome 96 and newer, no installation warning:

  "minimum_chrome_version": "96",
  "permissions": ["declarativeNetRequestWithHostAccess"],
  "host_permissions": ["*://*.example.com/"],
  "background": {"service_worker": "bg.js"},

bg.js:

const iframeHosts = [  'example.com',];
chrome.runtime.onInstalled.addListener(() => {
  chrome.declarativeNetRequest.updateDynamicRules({
    removeRuleIds: iframeHosts.map((h, i) => i + 1),
    addRules: iframeHosts.map((h, i) => ({
      id: i + 1,
      condition: {
        domains: [chrome.runtime.id],
        urlFilter: `||${h}/`,
        resourceTypes: ['sub_frame'],
      },
      action: {
        type: 'modifyHeaders',
        responseHeaders: [
          {header: 'X-Frame-Options', operation: 'remove'},
          {header: 'Frame-Options', operation: 'remove'},
        ],
      },
    })),
  });
});

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionIan McIntyre SilberView Question on Stackoverflow
Solution 1 - JavascriptRob WView Answer on Stackoverflow
Solution 2 - JavascriptwOxxOmView Answer on Stackoverflow