google-chromegoogle-chrome-extensionfirefox-addon-webextensions

MV3 declarativeNetRequest and X-Frame-Options DENY


I have a MV2 extension with chrome.webRequest that works perfectly but fail on MV3 declarativeNetRequest getting around iframes.

The extension is like a multi-messenger that opens multiple iframes for various sites to merge in a single extension all popular messengers.

So I have a domain "example.com" and there I open multiple iframes, for example open an iframe with Twitter.com or Telegram.org. Since twitter.com or telegram.org set the X-Frame-Options to DENY those iframes don't show anything. With MV2 we could run chrome.webRequest and remove those headers:

chrome.webRequest.onHeadersReceived.addListener(
    function (details)
    {
        if (details.tabId && (details.tabId === tabId || details.tabId === -1 || tabMultiId.includes(details.tabId))) {
            var b = details.responseHeaders.filter((details) => !['x-frame-options', 'content-security-policy', 'x-content-security-policy', 'strict-transport-security', 'frame-ancestors'].includes(details.name.toLowerCase()));
            
            b.forEach(function(e){
              "set-cookie" === e.name &&  -1 !== e.value.indexOf("Secure") && (-1 !== e.value.indexOf("SameSite=Strict") ? 
                            (e.value = e.value.replace(/SameSite=Strict/g, "SameSite=None"))
                            : -1 !== e.value.indexOf("SameSite=Lax")
                            ? (e.value = e.value.replace(/SameSite=Lax/g, "SameSite=None"))
                            : (e.value = e.value.replace(/; Secure/g, "; SameSite=None; Secure")));
            });
            
            return {
                responseHeaders: b
            }
        }
    },
    {
        urls: [ "<all_urls>" ],
        tabId: tabId
    },
    ["blocking", "responseHeaders", "extraHeaders"]
);

I have tried to do exactly the same with MV3 but keep failing. My 2 attemps:

async function NetRequest() {
var blockUrls = ["*://*.twitter.com/*","*://*.telegram.org/*"];
var tabId = await getObjectFromLocalStorage('tabId');
var tabMultiId = [];
tabMultiId = JSON.parse(await getObjectFromLocalStorage('tabMultiId'));
tabMultiId.push(tabId);
blockUrls.forEach((domain, index) => {
    let id = index + 1;
    
        chrome.declarativeNetRequest.updateSessionRules({
        addRules:[
            {
            "id": id,
            "priority": 1,
            "action": {     "type": "modifyHeaders",
                            "responseHeaders": [
                                { "header": "X-Frame-Options", "operation": "remove" },
                                { "header": "Frame-Options", "operation": "remove" },
                                { "header": "content-security-policy", "operation": "remove" },
                                { "header": "content-security-policy-report-only", "operation": "remove" },
                                { "header": "x-content-security-policy", "operation": "remove" },
                                { "header": "strict-transport-security", "operation": "remove" },
                                { "header": "frame-ancestors", "operation": "remove" },
                                { "header": "set-cookie", "operation": "set", "value": "SameSite=None; Secure" }
                            ] 
            },
            "condition": {"urlFilter": domain, "resourceTypes": ["image","media","main_frame","sub_frame","stylesheet","script","font","xmlhttprequest","ping","websocket","other"], 
            "tabIds" : tabMultiId }
            }
            ],
        removeRuleIds: [id]
        });
    
});
}

async function launchWindow(newURL, windowDimensions, urlWindow, isIncognitoWindow, windowType) {
    chrome.windows.create({ url: newURL, type: windowType, incognito: isIncognitoWindow, width: windowDimensions.width, height: windowDimensions.height, left: windowDimensions.left, top: windowDimensions.top },
        async function (chromeWindow) {
            if (urlWindow != "install" || urlWindow != "update") {
                chrome.storage.local.set({ 'extensionWindowId': chromeWindow.id }, function () { });
                chrome.storage.local.set({ 'tabId': chromeWindow.tabs[0].id }, function () { });
                NetRequest();
            }
    });
}

Also tried:

const iframeHosts = [
        'twitter.com', 'telegram.org'
      ];

      const RULE = {
        id: 1,
        condition: {
          initiatorDomains: ['example.com'],
          requestDomains: iframeHosts,
          resourceTypes: ['sub_frame', 'main_frame'],
        },
        action: {
          type: 'modifyHeaders',
          responseHeaders: [
            {header: 'X-Frame-Options', operation: 'remove'},
            {header: 'Frame-Options', operation: 'remove'},
          ],
        },
      };
      chrome.declarativeNetRequest.updateDynamicRules({
        removeRuleIds: [RULE.id],
        addRules: [RULE],
      });

Permissions:

  "permissions": [
    "system.display",
    "scripting",
    "activeTab",
    "notifications",
    "contextMenus",
    "unlimitedStorage",
    "storage",
    "declarativeNetRequestWithHostAccess",
    "webNavigation",
  "alarms"
  ],
  "host_permissions": [
  "<all_urls>"
  ],

Any of this attempts worked. Greetings and thank you very much for anyone that try to help.


Solution

    1. You need to unregister service worker for the site and optionally clear its cache using chrome.browsingData API.
    2. Syntax for urlFilter is different, so your "*://*.twitter.com/*" is incorrect and should be "||twitter.com/", however a better solution is to use requestDomains because it allows specifying multiple sites in just one rule.

    // manifest.json

      "permissions": ["browsingData", "declarativeNetRequest"],
      "host_permissions": ["*://*.twitter.com/", "*://*.telegram.org/"],
    

    // extension script

    async function configureNetRequest(tabId) {
      const domains = [
        'twitter.com',
        'telegram.org',
      ];
      const headers = [
        'X-Frame-Options',
        'Frame-Options',
      ];
      await chrome.declarativeNetRequest.updateSessionRules({
        removeRuleIds: [1],
        addRules: [{
          id: 1,
          action: {
            type: 'modifyHeaders',
            responseHeaders: headers.map(h => ({ header: h, operation: 'remove'})),
          },
          condition: {
            requestDomains: domains,
            resourceTypes: ['sub_frame'],
            tabIds: [tabId],
          },
        }],
      });
      await chrome.browsingData.remove({
        origins: domains.map(d => `https://${d}`),
      }, {
        serviceWorkers: true,
        // cacheStorage: true, // shouldn't be necessary normally
      });
    }
    

    // Usage

    chrome.windows.create({ url: 'about:blank' }, async w => {
      await configureNetRequest(w.tabs[0].id);
      await chrome.tabs.update(w.tabs[0].id, { url: 'https://some.real.url/' });
    });