Tampermonkey / tampermonkey

Tampermonkey is the most popular userscript manager, with over 10 million users. It's available for Chrome, Microsoft Edge, Safari, Opera Next, and Firefox.
GNU General Public License v3.0
4.24k stars 418 forks source link

TM5 fails to inject in raw sandbox mode with meta content-security-policy; 4.19 (w/ CSP modification disabled) works #1949

Closed darkrain42 closed 10 months ago

darkrain42 commented 10 months ago

Expected Behavior

TM 5.0 is unable to inject successfully in a 'raw' sandbox mode for pages that define a CSP via a meta header, and always falls back to the Javascript sandbox. For the same page with the same userscript, TM 4.19 has no issues even if I turn off Tampermonkey's modifications of the CSP header ("Modify existing content security policy (CSP) headers").

This is similar to #1934, except that I've never had to configure the "Instant Injection" setting for this userscript. It's possibly a duplicate of #1919

I (and my users) started seeing this problem with the official release of TM 5.0, but my testing for filing this bug report was done with the Beta build (5.0.6191).

Actual Behavior

In a fresh Firefox profile, these get a "raw" sandbox:

These see a Javascript sandbox, and page content has trouble accessing the things I'm overriding:

For testing purposes, I consider the test a pass if:

(Things do work if I rewrite using exportFunction and cloneInto)

Specifications

Script

// ==UserScript==
// @name        Test
// @version     0.14.1
// @author      darkrain
// @description mmm
// @homepage    https://darkrain42.org/
// @match       https://darkrain42.org/unindexed/csp/test.html
// @sandbox     raw
// @run-at      document-start
// @grant       unsafeWindow
// ==/UserScript==

(function () {
    'use strict';

    console.log("GM_info.relaxedCsp", GM_info.relaxedCsp);
    console.log("GM_info.sandboxMode", GM_info.sandboxMode);
    console.log("GM_info", GM_info);

    const origWeakMap = WeakMap;
    class ottersWeakMap extends WeakMap {};
    unsafeWindow.WeakMap = ottersWeakMap;
})();

The content of the page I'm testing with is reproduced below.

<!DOCTYPE html>
<html lang="en-US" dir="ltr">
    <head>
        <title>Otters</title>
        <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'nonce-12345678901234567890123456789012' chrome-extension: 'unsafe-inline' 'unsafe-eval' https://example.com blob:; object-src 'self' https://example.com; style-src 'self' blob: chrome-extension: 'unsafe-inline' https://example.com; img-src 'self' data: blob: https://example.com https:; media-src 'self' https://example.com blob:; frame-src blob: mailto: https://example.com https:; font-src 'self' https: data: https://example.com; connect-src 'self' https://example.com blob: https://example.com wss://example.com">
        <meta name="viewport" content="initial-scale=1.0, maximum-scale=3.0, minimum-scale=1.0, minimal-ui, viewport-fit=cover"/>
        <meta name="viewport" id="vp" media="(device-height: 568px)" content="initial-scale=1.0,maximum-scale=3, minimal-ui, viewport-fit=cover"/>
        <link rel="apple-touch-icon" href="apple-touch-icon-ipad.png"/>
        <meta name="apple-mobile-web-app-capable" content="yes"/>
        <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"/>
        <meta name="format-detection" content="telephone=no"/>
        <meta name="msapplication-tap-highlight" content="no"/>
        <meta data-requestid="42" name="meta-app-referer" data-referer="None"/>
        <meta http-equiv="origin-trial" content="1234567"/>
        <style></style>
    </head>
    <body class="desktop">
        <style></style>
<script nonce="12345678901234567890123456789012">
    setTimeout(() => {
        console.log("page script running");
        const x = new WeakMap();
        console.log("page script done");
    }, 100);
</script>
    </body>
</html>
derjanb commented 10 months ago

default-src 'self'; script-src 'self' 'nonce-12345678901234567890123456789012' chrome-extension: 'unsafe-inline' 'unsafe-eval' https://example.com blob:; object-src 'self' https://example.com; style-src 'self' blob: chrome-extension: 'unsafe-inline' https://example.com; img-src 'self' data: blob: https://example.com https:; media-src 'self' https://example.com blob:; frame-src blob: mailto: https://example.com https:; font-src 'self' https: data: https://example.com; connect-src 'self' https://example.com blob: https://example.com wss://example.com

The "problem" is the CSP allows unsafe-eval, but no unsafe-inline, even though it is stated there, inline scripts are forbidden because of the required nonce nonce-12345678901234567890123456789012.

TM 4.19.0 tries eval first and the falls back to inline scripts. This worked in many cases, because TM actively relaxed the CSP (if sent via header). CSP relaxing is kind of unwanted behavior and I reworked TM to not do it by default, while still being able to inject inline scripts if the CSP is sent via header.

In this case TM 4.19.0 is able to run the script in raw mode, because the meta tag CSP allows eval. Is this a real life CSP? It is quite unusual to allow eval, but then restrict inline scripts to use a nonce and therefore actively forbid unsafe inline scripts.

derjanb commented 10 months ago

Note: Setting Modify existing content security policy (CSP) headers to Yes and enabling Add Tampermonkey to the HTML's CSP successfully relaxes the meta tag CSP.

derjanb commented 10 months ago

So after thinking a little bit further about this I tend to close this as "not planned". Sorry.

Please use "Content Script API" "UserScripts API Dynamic" to inject the script before the meta CSP is applied or even better prepare your code to run in "js" mode.

darkrain42 commented 10 months ago

Is this a real life CSP? It is quite unusual to allow eval, but then restrict inline scripts to use a nonce and therefore actively forbid unsafe inline scripts.

Yeah. That's the CSP policy for a Salesforce Lightning domain ("blah.lightning.force.com"), at least the one I deal with. I sanitized all the actual hostnames / nonce values, but otherwise preserved the structure of the meta tag.

Please use "Content Script API" "UserScripts API Dynamic" to inject the script before the meta CSP is applied or even better prepare your code to run in "js" mode.

Alright. Thanks for taking a look!