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.37k stars 428 forks source link

Cannot inject functions into unsafeWindow on Twitter #2223

Closed Zezombye closed 3 weeks ago

Zezombye commented 3 weeks ago

Expected Behavior

Adding functions to unsafeWindow works, eg on youtube:

// ==UserScript==
// @name         New Userscript
// @namespace    http://tampermonkey.net/
// @version      2024-11-02
// @description  try to take over the world!
// @author       You
// @match        https://www.youtube.com/watch?v=Bv1LVYtdGGo
// @icon         https://www.google.com/s2/favicons?sz=64&domain=youtube.com
// @grant        unsafeWindow
// @run-at       document-start
// ==/UserScript==

(function() {
    'use strict';

    unsafeWindow.test1 = "test"
    unsafeWindow.test2 = () => "test"
})();

image

Actual Behavior

Running the following userscript gives:

// ==UserScript==
// @name         New Userscript
// @namespace    http://tampermonkey.net/
// @version      2024-11-02
// @description  try to take over the world!
// @author       You
// @match        https://x.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=x.com
// @grant        unsafeWindow
// @run-at       document-start
// ==/UserScript==

(function() {
    'use strict';

    unsafeWindow.test1 = "test"
    unsafeWindow.test2 = () => "test"
})();

image

Functions cannot be accessed in any way. (Probably a CSP issue?)

Tested while disabling all other scripts.

Specifications

derjanb commented 3 weeks ago

Yes, CSP doesn't allow Tampermonkey to inject the script into the page, therefore it is executed in a FF-specific sandbox. This means you have to use exportFunction to make the function available to the page.

https://github.com/Tampermonkey/tampermonkey/issues/1636#issuecomment-1781733309

Zezombye commented 3 weeks ago

My actual use case is overriding XMLHttpRequest.open() (since Twitter hasn't switched to fetch() after all, my script failing was due to the CSP change).

ChatGPT suggested me to use exportFunction then eval():

(function() {
    // This function will override XMLHttpRequest.prototype.open
    function overrideXHROpen() {
        var open_prototype = XMLHttpRequest.prototype.open;
        XMLHttpRequest.prototype.open = function() {
            console.log("test");
            this.addEventListener('readystatechange', function(event) {
                if (this.readyState === 4) {
                  //...
                }
            });
            return open_prototype.apply(this, arguments);
        };
    }

    // Use exportFunction to make the function available in the page's context
    var overrideFunction = exportFunction(overrideXHROpen, window, { defineAs: 'overrideXHROpen' });

    // Inject the function into the page context and execute it
    window.eval('(' + overrideFunction + ')();');
})();

But eval() is also blocked by CSP.

It then suggested me to inject as a script element:

        // Convert the function into a string and inject it as a script element
        var script = document.createElement('script');
        script.textContent = '(' + overrideXHROpen.toString() + ')();';
        document.documentElement.appendChild(script);

        // Remove the script after execution
        script.parentNode.removeChild(script);

But same thing, CSP blocks inline scripts without nonce.

It then gave up and suggested to straight up modify the HTTP request (which btw would be a neat addition to TM).

Is it possible for TamperMonkey to modify CSP to be able to inject its own scripts?