User Scripts vs injected scripts with eval()

201 views
Skip to first unread message

Likely Logic

unread,
Feb 14, 2025, 10:32:19 AMFeb 14
to Chromium Extensions
I'm working on an extension which in MV2 injected runnable scripts into the page.

In MV3 of course we have to execute prepared functions, with injected arguments.

Right now, it seems we can execute a prepared function so that it does something like this:

chrome.scripting.executeScript({
  target: { tabId },
  func: (code) => {
    eval(code)
  },
  args: ['alert("hello world")']
})

This actually runs!

However, I'm not sure if it would pass review. Can anyone confirm?

To hedge my bets, I'm looking at user scripts.

I've gotten this working, but it seems there is a caveat; a registered user script will only run after a page has loaded, so if the user is already on the page, they have to reload.

Is there a way to have the registered scripts run immediately?

Thanks!

woxxom

unread,
Feb 14, 2025, 4:59:57 PMFeb 14
to Chromium Extensions, Likely Logic
While waiting for an official response, logically it should depend on what your extension does. If its purpose is to run user-provided code snippets and it's unequivocally stated then it's probably fine.

Currently you can use eval() inside onMessage listener as shown here: https://stackoverflow.com/a/77579988
In the near future Chrome will enable an on-demand execute() method, see https://groups.google.com/a/chromium.org/g/chromium-extensions/c/GjGJNbY-RtE/m/56AJVttREQAJ

P.S. For readers who might want to use the posted code, it's incomplete and won't work as written, because by default injection occurs in the isolated world which cannot use eval(). It can only work if you use world: 'USER_SCRIPT' or 'MAIN'. The former requires the "userScripts" permission, which currently has a nonsensical requirement for the user to enable the developer mode switch in chrome://extensions as well as calling chrome.userScripts.configureWorld to enable unsafe-eval in the CSP. The latter is affected by the CSP of the webpage and thus won't work on sites that block unsafe-eval or require TrustedTypes e.g. https://groups.google.com. You'd have to strip the CSP via declarativeNetRequest, but even that won't help on sites that use a <meta> tag for CSP.

Likely Logic

unread,
Feb 14, 2025, 5:37:54 PMFeb 14
to Chromium Extensions, woxxom, Likely Logic
Hey woxxom,

Thanks for the detailed info! You area a treasure trove of info as usual.

The extension runs code in the MAIN world, because it the code it runs needs to access a loaded JS lib.

Currently it IS working in the target website, so I presume that the CSP is not set up to be that strict.

The thing I'm not quite sure about the link you posted is there's quite a convoluted setup to achieve something, but I'm not sure why the gymnastics are necessary when this seems to work already?

Perhaps you would be so kind as to explain.

Thank you.


woxxom

unread,
Feb 14, 2025, 5:43:06 PMFeb 14
to Chromium Extensions, Likely Logic, woxxom
The link I gave is an answer to your question "Is there a way to have the registered scripts run immediately?"

Oliver Dunk

unread,
Feb 17, 2025, 9:17:39 AMFeb 17
to woxxom, Chromium Extensions, Likely Logic
Hi both,

The User Scripts API is definitely the way to go here. As wOxxOm mentioned, starting in Chrome 135 we'll have the `userScripts.execute` method. You can use that to immediately run a script without needing to reload the page.

As was also mentioned, I wouldn't expect the snippet provided to work as that violates the CSP of the isolated world content scripts are running in.

Our policies are not quite as explicit as I would like them to be when it comes to running code from a non-remote source in the main world. However, my understanding (which I believe is covered by policy) is that we also wouldn't want to see `executeScript` used in this way. Depending on the exact implementation I could see it being flagged as either hard for us to verify that the arguments are coming from local data, or that an API is being used for the wrong purpose. Given those points I wouldn't encourage building an extension that relies on this.
Oliver Dunk | DevRel, Chrome Extensions | https://developer.chrome.com/ | London, GB


--
You received this message because you are subscribed to the Google Groups "Chromium Extensions" group.
To unsubscribe from this group and stop receiving emails from it, send an email to chromium-extens...@chromium.org.
To view this discussion visit https://groups.google.com/a/chromium.org/d/msgid/chromium-extensions/2059c016-1624-4663-895d-f7b3a4506fd2n%40chromium.org.

Likely Logic

unread,
Feb 17, 2025, 4:33:42 PMFeb 17
to Chromium Extensions, Oliver Dunk, Chromium Extensions, Likely Logic, woxxom
Hey Oliver!

Thanks for your input. FWIW the provided snippet does already work in its intended site and for its use case, in MV3.

The use case is, I'm helping to migrate the following extension to MV3:

https://chromewebstore.google.com/detail/wfx-for-workflowy/jbehgpdjkcconnaagjhddddfdajbpfhi?pli=1

WorkFlowy basically has its own JS API loaded in the browser (under the global WF in the main world) and this extension allows the user to package arbitrary JS that (mainly) plugs into this JS API so it can perform tasks, such as asking for user input, searching data in the site, or performing actions to manipulate the data.

Regarding this in context of `executeScript()`, surely, this is the essence of "user" scripting?

Perhaps I'm missing something but I don't understand how the team could ever verify where code or arguments were coming from anyway?

Perhaps you can advise!

woxxom

unread,
Feb 17, 2025, 4:52:56 PMFeb 17
to Chromium Extensions, Likely Logic, Oliver Dunk, Chromium Extensions, woxxom
>  FWIW the provided snippet does already work in its intended site and for its use case, in MV3

It can't work by design, assuming eval() is the built-in JS function and you didn't specify world:'MAIN', otherwise it would make half of 5-year MV3 effort on disabling remote code a total joke. Note that chromium's definition of "remote" also includes the eval function and other ways to run a string of JS code like `setTimeout('code')` or `new Function('code')`.

Skimming through your current MV2 code in the web store I see it doesn't seem to use any user code (external JS), but rather constructs literal strings with code inside and some arguments in stringified form. Assuming you want to run theses snippets **synchronously** and control the execution from the content script (not from the background script), you can inject two content scripts: isolated.js (without specifying "world") and main.js (in "world":"MAIN"). You can do it by declaring both in manifest.json or registering/injecting via chrome.scripting API in the background  script. These two scripts can send data to each other **synchronously** via CustomEvent (or MouseEvent for DOM nodes) e.g. https://stackoverflow.com/a/19312198. The code that's currently stringified in your MV2 extension will be in main.js as a normal JS function which will be called upon receiving a message.

Juraj M.

unread,
Feb 18, 2025, 3:46:05 AMFeb 18
to Chromium Extensions, woxxom, Likely Logic, Oliver Dunk, Chromium Extensions
> otherwise it would make half of 5-year MV3 effort on disabling remote code a total joke
Well, I have some bad news :), ... (who am I kidding, I'm sure you already know :D)
You can simply remove CSP from page (or all pages), and then execute your string in the MAIN world of the page without any restrictions.
WARNING: this is not a "How to" tutorial, don't do this!

Oliver Dunk

unread,
Feb 18, 2025, 5:22:39 AMFeb 18
to Juraj M., Chromium Extensions, woxxom, Likely Logic
A lot of this is about the distinction between what is technically possible, and what we allow through policy. There is definitely scope to do more in Chromium, but there are also some things that are technically possible but which could be flagged during review.

Perhaps I'm missing something but I don't understand how the team could ever verify where code or arguments were coming from anyway?

This is the root of the problem. Calling `eval` in this way looks very similar to an approach that might be used to circumvent remote hosted code restrictions. The safest option would be to use the `chrome.userScripts` API which is explicitly exempt from the Remote Hosted Code policy and therefore is definitely policy compliant.

Skimming through your current MV2 code in the web store I see it doesn't seem to use any user code (external JS), but rather constructs literal strings with code inside and some arguments in stringified form. Assuming you want to run theses snippets **synchronously** and control the execution from the content script (not from the background script), you can inject two content scripts: isolated.js (without specifying "world") and main.js (in "world":"MAIN"). You can do it by declaring both in manifest.json or registering/injecting via chrome.scripting API in the background  script. These two scripts can send data to each other **synchronously** via CustomEvent (or MouseEvent for DOM nodes) e.g. https://stackoverflow.com/a/19312198. The code that's currently stringified in your MV2 extension will be in main.js as a normal JS function which will be called upon receiving a message.

If this approach works for you, it sounds like all of your code would be packaged in the extension and you would no longer need `eval`. That said, I do see some screenshots on your Chrome Web Store page that include an "Edit JavaScript" section. That very much looks like a situation for the `chrome.userScripts` API.
Oliver Dunk | DevRel, Chrome Extensions | https://developer.chrome.com/ | London, GB

woxxom

unread,
Feb 18, 2025, 10:39:13 AMFeb 18
to Chromium Extensions, Oliver Dunk, Chromium Extensions, woxxom, Likely Logic, Juraj M.
>  execute your string in the MAIN world of the page without any restrictions.

The main world was never a part of the effort I mentioned i.e. restricting remote code execution on the platform level. I referred to the author's claim that the built-in JS function eval() can run in the isolated world.

Likely Logic

unread,
Feb 18, 2025, 3:23:08 PMFeb 18
to Chromium Extensions, woxxom, Oliver Dunk, Chromium Extensions, Likely Logic, Juraj M.
Yes, I think some of the confusion in this thread is between how to execute immediately (in mv3) and whether or not it can or should be done.

To save any further commenting, I'll just say that the workarounds for v3 look a bit convoluted and bonkers, but I'll try them out in due course, and assuming they do the trick, all I have to do is wait for direct execution in mv3 in a couple of versions time.

Thanks all! 🙏

Reply all
Reply to author
Forward
0 new messages