Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sidebar-ContentScript (manifest V3) not working #47

Open
radiolondra opened this issue Nov 17, 2022 · 14 comments
Open

Sidebar-ContentScript (manifest V3) not working #47

radiolondra opened this issue Nov 17, 2022 · 14 comments

Comments

@radiolondra
Copy link

Just to confirm this post on Samples.
The error is always the same (see picture below)
This happens while opening the majority of websites (e.g. github.com and tons of others).

mingError

@radiolondra
Copy link
Author

radiolondra commented Nov 17, 2022

Installing my test extension on Edge I can see at least the meaning of the exception:

mingErrorEdge

It seems that somewhere in code ther's still a usage of eval. With Manifest V3 unsafe-eval in content-security-police is not allowed (and doesn't exist) anymore. And the error is thrown. Using Manifest V2 (now dead) everything works.

Any idea about how to solve this?

@mingyaulee
Copy link
Owner

I have investigated with a simple wasm extension and it produces the same behaviour. I have filed a bug report in Chromium here.

@scottkuhl
Copy link

@mingyaulee or @radiolondra with this Chromium bug in place, does it mean that no Blazor based browser extension will work with Manifest V3?

@mingyaulee
Copy link
Owner

All web assembly extensions, not just Blazor, are affected by this bug. The extensions that loads web assembly in content scripts are subjected to the CSP of the page the user is browsing, instead of the CSP declared in the extension manifest. Therefore, the content script works in some pages and not in those with strict CSP.

@scottkuhl
Copy link

Thanks @mingyaulee . That's quite the show stopper for web assembly based extensions.

@scottkuhl
Copy link

"The extensions that loads web assembly in content scripts are subjected to the CSP of the page the user is browsing"

@mingyaulee so could you create a browser extension with Blazor, as long as the content scripts were written in just JavaScript?

"content_scripts": [
  {
    "matches": [ "*://*/*" ],
    "js": [ "ContentScript.js" ]
  }
],

@mingyaulee
Copy link
Owner

"The extensions that loads web assembly in content scripts are subjected to the CSP of the page the user is browsing"

@mingyaulee so could you create a browser extension with Blazor, as long as the content scripts were written in just JavaScript?

"content_scripts": [
  {
    "matches": [ "*://*/*" ],
    "js": [ "ContentScript.js" ]
  }
],

Yes that is correct.

@LostBeard
Copy link

LostBeard commented Feb 7, 2024

I have my Blazor WASM V3 browser extension working on every page using the below declarativeNetRequest rule to remove CSP rules in page response headers. Not a perfect solution, but it works now.

{
   id: 1,
   action: {
       type: 'modifyHeaders',
       responseHeaders: [
           {
               header: 'content-security-policy',
               operation: 'remove'
           }
       ]

   },
   condition: {
       urlFilter: "|https*",
       resourceTypes: ["main_frame", "sub_frame"]
   }
}

I am not using this repo but it is helpful. @mingyaulee Thank you.

@mingyaulee
Copy link
Owner

I have my Blazor WASM V3 browser extension working on every page using the below declarativeNetRequest rule to remove CSP rules in page response headers. Not a perfect solution, but it works now.

{
   id: 1,
   action: {
       type: 'modifyHeaders',
       responseHeaders: [
           {
               header: 'content-security-policy',
               operation: 'remove'
           }
       ]

   },
   condition: {
       urlFilter: "|https*",
       resourceTypes: ["main_frame", "sub_frame"]
   }
}

I am not using this repo but it is helpful. @mingyaulee Thank you.

I'd imagine this would be rejected when publishing to the Chrome extension store because installing the extension which removes the content security policy header will make the user's browser vulnerable to XSS attacks.

@LostBeard
Copy link

LostBeard commented Feb 14, 2024

I'm sure it would be.

Another method is to have the content page message the background script (extension service worker) when loading Blazor WASM fails due to CSP by listening for the onsecuritypolicyviolation event. Then the background script can add 'wasm-unsafe-eval' to the 'originalPolicy' (supplied by the onsecuritypolicyviolation event) 'script-src' section and create a dynamic rule with a urlFilter for that page that will 'set' the 'content-security-policy' header to your updated security policy next page load.

Still not ideal, but much better than removing CSP completely or not working at all.

@LostBeard
Copy link

LostBeard commented Feb 14, 2024

I am using github.com to test fixes / workarounds for the WebAssembly.instantiateStreaming issue due to CSP violations in extension content mode. My Blazor extension is working on github.com with the method above.

Hopefully they get this fixed soon.

@mingyaulee Thank you for filing the bug report and getting that ball rolling.

@LostBeard
Copy link

LostBeard commented Feb 15, 2024

The below is working in Chrome. I haven't tested Firefox yet. This patches CSP rules to include 'wasm-unsafe-eval' and reloads the tab to allow Blazor to load.

in content script before trying to load Blazor WASM

function onSecurityPolicyViolation(e) {
    // chrome - e.blockedURI === "wasm-eval"
    if ((e.blockedURI === "wasm-eval" || e.blockedURI === "wasm-unsafe-eval")
        && e.violatedDirective === "script-src"
        && e.originalPolicy.indexOf('wasm-unsafe-eval') === -1) {
        document.removeEventListener('securitypolicyviolation', onSecurityPolicyViolation);
        var cspViolation = {
            documentURI: e.documentURI,                 // document.location.href
            originalPolicy: e.originalPolicy,           // csp header value
            // atm only the above to vars are used in the background script. below are informative
            blockedURI: e.blockedURI,                   // "wasm-eval"
            disposition: e.disposition,                 // "enforce"
            effectiveDirective: e.effectiveDirective,   // "script-src"
            sourceFile: e.sourceFile,                   // "chrome-extension" (not sure on firefox, others)
            violatedDirective: e.violatedDirective,     // "script-src"
        };
        browser.runtime.sendMessage({ cspViolation });
    }
}
document.addEventListener('securitypolicyviolation', onSecurityPolicyViolation);

in background (service worker) script

browser.runtime.onMessage.addListener(function (request, sender) {
    if (request.cspViolation) {
        patchCSP(request, sender);
    }
});
async function patchCSP(request, sender) {
    if (request.cspViolation) {
        var originalPolicy = request.cspViolation.originalPolicy;
        var updatedPolicy = originalPolicy;
        if (originalPolicy.indexOf('wasm-unsafe-eval') === -1) {
            updatedPolicy = originalPolicy.replace('script-src ', "script-src 'wasm-unsafe-eval' ");
        } else {
            // rule already has 'wasm-unsafe-eval'
            // if this happens, there is another problem
            return;
        }
        var url = new URL(request.cspViolation.documentURI);
        // separate paths on the same domain may have different csp rules
        // the query string and hash shouldn't have any effect on csp rules 
        var pageUrl = url.origin + url.pathname;
        var pageUrlEscaped = escapeRegExp(pageUrl);
        var cspRule = {
            id: await getFreeSessionRuleId(),
            action: {
                type: 'modifyHeaders',
                responseHeaders: [
                    {
                        header: 'content-security-policy',
                        operation: 'set',
                        value: updatedPolicy,
                    }
                ]

            },
            condition: {
                regexFilter: `^${pageUrlEscaped}(\\?.*)?(#.*)?$`,
                resourceTypes: ["main_frame", "sub_frame", "xmlhttprequest"]
            }
        };
        console.log('Adding rule', cspRule);
        // save rule
        await browser.declarativeNetRequest.updateSessionRules({
            addRules: [cspRule]
        });
        // reload tab so the new rule can take effect
        browser.tabs.reload(sender.tab.id);
    }
}
async function getFreeSessionRuleId() {
    var previousRules = await browser.declarativeNetRequest.getDynamicRules();
    var previousRuleIds = previousRules.map(rule => rule.id);
    if (previousRuleIds.length === browser.declarativeNetRequest.MAX_NUMBER_OF_SESSION_RULES) {
        await browser.declarativeNetRequest.updateSessionRules({ removeRules: previousRuleIds });
        previousRuleIds = [];
    }
    var availId = Math.floor(Math.random() * 1000000) + 1;
    while (previousRuleIds.indexOf(availId) !== -1) availId = Math.floor(Math.random() * 1000000) + 1;
    return availId;
}
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping
function escapeRegExp(string) {
    return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string
}

@mingyaulee
Copy link
Owner

From my understanding of the API, the rules are isolated from other extensions, so you can just keep track of the latest running ID in the local storage.

@LostBeard
Copy link

LostBeard commented Feb 17, 2024

After finally getting my Blazor extension working in Firefox I am surprised to see that Firefox does not have the 'wasm-unsafe-eval' issue that Chrome has. Tested GitHub and it is working. Yay!

What was surprising is that, in extension content scripts/pages, Firefox wraps the return value from Resposne.json, Resposne.arrayBuffer, and Resposne.blob in an XrayWrapper. The cryptic error Error: Not allowed to define cross-origin object as property on [Object] or [Array] XrayWrapper was hard to diagnose. All because Blazor was fetching some json and then trying to modify it (blazor.boot.json.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants