Injecting & executing string functions string functions in chrome extension.

445 views
Skip to first unread message

Sumit Kumar

unread,
Apr 8, 2024, 7:49:53 AM4/8/24
to Chromium Extensions
Hi All,
I am migrating a chrome extension from manifest v2 to v3. In my case the extension code in manifest v2 is heavily dependent on string functions coming from the API response.
I checked on various resources and everywhere it is mentioned that now we cannot use eval or Function constructor to execute string functions.

For example:
```
"steps": [
        {
            "name": "Set report level",
            "context": "Background",
            "code": "function(api) {\n      var userContext = api.getUserContext();\n      userContext.reportLevel = \"normal\";\n    }"
        },
```
The above snippet is part of API response. The code part contains a string function and we need to inject it into the page it will do it's work and call the next step if successful.
In MV2, there was a code property in the chrome.tabs.executeScript() to handle such code injection but now the code property is removed in MV3. So, how can I handle such cases in MV3.

manifest.json
-----------------------
{
     ...,
     "content_security_policy": {
              "extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self';"
      },
}
The above is my current manifest.json file.

Please help with the above issue. If anything is unclear, please comment and I will provide the information.

Thanks

Oliver Dunk

unread,
Apr 8, 2024, 7:52:43 AM4/8/24
to Sumit Kumar, Chromium Extensions
Hi Sumit,

Evaluating remotely hosted code is explicitly forbidden in Manifest V3. You can read more in our program policies here: https://developer.chrome.com/docs/webstore/program-policies/mv3-requirements/

In the example you shared, is the report level ("normal" in this case) the only thing that changes? If so, could you bundle the rest of the code with your extension and simply have the API return the new level?
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 on the web visit https://groups.google.com/a/chromium.org/d/msgid/chromium-extensions/1d05ca5d-c0f8-4924-8cc9-14239f2f2063n%40chromium.org.

Sumit Kumar

unread,
Apr 8, 2024, 8:50:40 AM4/8/24
to Chromium Extensions, Oliver Dunk, Chromium Extensions, Sumit Kumar, Rahul Yadav, will.m...@oneidentity.com, Chirag Zakhmi
Hi Oliver,
Thanks for your reply. The example I provided was just a sample of the response. There are other steps involved which are provided in the below snippet. So are you suggesting to hard code these steps on the client side and replacing the dynamic values such as username, password. and subdomain which is required in step 5?

Thanks

```
"steps": [
        {
            "name": "Set report level",
            "context": "Background",
            "code": "function(api) {\n      var userContext = api.getUserContext();\n      userContext.reportLevel = \"normal\";\n    }"
        },
        {
            "name": "Cloak",
            "context": "Content",
            "code": "function(api) {\n      if (api.cloak) {\n        // api.cloak();\n      } else {\n        api.log(\"cloak not supported\");\n      }\n      return api.NEXT;\n    }"
        },
        {
            "name": "Go to login URL",
            "context": "Content",
            "code": "function(api) {\n      var loginUrl = api.getLoginUrl();\n      // var match = /https?:\\/\\/([^\\/]+)/.exec(loginUrl);\n      // if (!!match && match[1]) {\n      // \tapi.clearCookie(match[1], /.*/);\n      // }\n      api.setLocation(loginUrl);\n      return api.NEXT;\n    }"
        },
        {
            "name": "Wait to stabilize page",
            "context": "Content",
            "code": "function(api) {\n      if (!document.body) {\n        api.log(\n          \"preMadeSteps.readyState document.body is not defined -> api.WAIT\"\n        );\n        return api.WAIT;\n      }\n      if (api.waitForReady) {\n        api.waitForReady();\n      } else {\n        api.log(\"wait for ready not supported\");\n      }\n      api.log(\"wait for ready finished\");\n      return api.NEXT;\n    }"
        },
        {
            "name": "Onelogin script",
            "context": "Content",
            "code": "function(api) {\n  if (!document.body) {\n    api.log(\"defaultEncaseFnString document.body is not defined -> api.WAIT\");\n    return api.WAIT;\n  }\n  var encasedFn = function perform_login(doc) {\n\n    var disable_browser_password_manager = true;\n    var auto_submit = true;\n\n    if (doc.getElementById('onelogin_dialog'))\n        doc.getElementById('onelogin_dialog').style.visibility='hidden';\n\n    var fieldlist = new Array();\n\n    \n       \n\n       fieldlist.push(['password','wltec6UPtpj,}#'])\n    \n       \n\n       fieldlist.push(['subdomain','onelogininc'])\n    \n       \n\n       fieldlist.push(['name','te...@quest.com'])\n    \n\n    var forms = doc.getElementsByTagName('form');\n    var submit_button = null;\n\n    var reg = /^post$/;\n\n    for (var i=0; i<forms.length; i++) {\n        var form_attr = forms[i].attributes.getNamedItem('method');\n        if (form_attr && reg.test(form_attr.value)) {\n            var form = forms[i];\n            break;\n        }\n    }\n\n    //Did not find a form in the document\n    if (i==forms.length) {\n\n        var getLocalIframes = function(doc) {\n\n            var orig_iframes = doc.getElementsByTagName('iframe');\n\n\n\t\t    var ret_iframes = [];\n\t\t    for (var i=0;i<orig_iframes.length;i++) {\n\t\t\t    try {\n\t\t\t\t    if (orig_iframes[i].contentDocument) {\n\t\t\t\t\t    ret_iframes.push(orig_iframes[i]);\n \t\t            }\n\t\t\t    } catch(e) {\n\t\t\t\t    continue;\n\t\t\t    }\n\t\t    }\n\t\t    return ret_iframes;\n\t    }\n\n        var iframes = getLocalIframes(document);\n\n        if (iframes.length > 0) {\n            for (var k=0;k<iframes.length;k++) {\n                var iframe = iframes[k];\n                var doc = iframe.contentDocument;\n                var forms = doc.forms;\n                for (var l=0;l<forms.length;l++) {\n                    var form = forms[l];\n                    var form_attr = form.attributes.getNamedItem('method');\n                    if (form_attr && reg.test(form_attr.value)) break;\n                }\n            }\n        }\n    }\n\n    //we did not find any forms, nor on the main document, nor on local iframes.\n    if (!form) return;\n\n    var fields = form.getElementsByTagName('input');\n\n    if (fields.length == 0)\n        fields = form.elements;\n\n    for (var j=0; j<fields.length; j++) {\n\n    \n       if (f = fields[j].attributes.getNamedItem('id')) {\n         if (f.value == 'submit' ) {\n            submit_button = fields[j];\n         }\n        }\n    \n\n    for (var n=0; n<fieldlist.length; n++) {\n       if (f = fields[j].attributes.getNamedItem('name')) {\n          if (f.value == fieldlist[n][0])\n             fields[j].value = fieldlist[n][1];\n           try {\n               if (fields[j].type == 'password' && disable_browser_password_manager) {\n                   var passField = fields[j];\n                   if (passField.getAttribute && passField.setAttribute)\n                       passField.setAttribute(\"type\", \"hidden\");\n               }\n           } catch(err) {};\n       }\n    }\n    }\n\n    \n      var buttons = form.getElementsByTagName('button');\n         for (var j=0; j<buttons.length; j++) {\n             if (f = buttons[j].attributes.getNamedItem('id')) {\n                 if (f.value == 'submit' ) {\n                     submit_button = buttons[j];\n                 }\n             }\n         }\n\n   if (auto_submit) {\n    if (submit_button != null) {\n        submit_button.click();\n    } else {\n        form.submit();\n        }\n   }\n\n    \n\n    }\n;\n  return encasedFn(document) || api.NEXT;\n}"
        },
        {
            "name": "Uncloak",
            "context": "Background",
            "code": "function(api) {\n      api.uncloak();\n    }"
        }
    ],
```


Oliver Dunk

unread,
Apr 8, 2024, 3:09:17 PM4/8/24
to Sumit Kumar, Chromium Extensions, Rahul Yadav, will.m...@oneidentity.com, Chirag Zakhmi
Hi Sumit,

Exactly! That means we can easily review the logic that will be executed (and the full extent of what is possible) and the only unknown is the specific parameters.

It also means less data to send over the network :)

Oliver Dunk | DevRel, Chrome Extensions | https://developer.chrome.com/ | London, GB

Sumit Kumar

unread,
Apr 9, 2024, 2:06:54 AM4/9/24
to Chromium Extensions, Oliver Dunk, Chromium Extensions, Rahul Yadav, will.m...@oneidentity.com, Chirag Zakhmi, Sumit Kumar
Hi Oliver,

Absolutely, reviewing the logic beforehand does indeed provide clarity and helps streamline the process, especially in terms of network efficiency.

Regarding the discussion about hardcoding code logic, I completely understand the appeal of such an approach for its predictability and reduced data transmission. However, I must highlight a crucial point that warrants consideration.

In our system, the steps don't always contain similar code; there's considerable variation. Moreover, we have numerous instances where scripts are dynamically injected based on responses from the server. This dynamic nature means that hardcoding might inadvertently disrupt the functionality and isn't a viable solution in our case. This is a single instance of the response coming from server, but there are different scripts for different purposes being sent from the server.
So, we need a way to execute the scripts. I explored the option for userScripts but they are also missing a very significant feature to execute script in a specific tab. Thanks


Oliver Dunk

unread,
Apr 9, 2024, 7:23:47 PM4/9/24
to Sumit Kumar, Chromium Extensions, Rahul Yadav, will.m...@oneidentity.com, Chirag Zakhmi
Hi Sumit,

The User Scripts API does seem like a good approach here. We're aware of the limitation around executing a one-time script immediately, and actually put together a proposal for a new API method that we are working on implementing: https://github.com/w3c/webextensions/blob/main/proposals/user-scripts-execute-api.md

If you need something shorter term, you could also look at the chrome.debugger API. This is slightly lower level but also allows execution of arbitrary scripts. Happy to share some code if it would be helpful.

In both cases, we recently updated our policies to make it explicit that these are allowed: https://developer.chrome.com/docs/webstore/program-policies/mv3-requirements/
Oliver Dunk | DevRel, Chrome Extensions | https://developer.chrome.com/ | London, GB

Sumit Kumar

unread,
Apr 12, 2024, 1:26:23 AM4/12/24
to Chromium Extensions, Oliver Dunk, Chromium Extensions, Rahul Yadav, will.m...@oneidentity.com, Chirag Zakhmi, Sumit Kumar
Hi Oliver,

Thanks for the update and for sharing the proposal for the User Scripts API. It's encouraging to see that there are efforts underway to address the limitation we've encountered. I've gone through the proposal and it looks promising.

One concern I have with the User Scripts API, however, is that while it allows the execution of scripts, it doesn't provide a straightforward way to retrieve the returned value from the executed script. In our workflow, it's essential to capture this returned value and pass it on to the next function. Do you have any insights or suggestions on how we could achieve this within the context of the User Scripts API?

Looking forward to your thoughts and guidance on this matter.

Thanks,
Sumit

Oliver Dunk

unread,
Apr 26, 2024, 11:29:50 AM4/26/24
to Sumit Kumar, Chromium Extensions, Rahul Yadav, will.m...@oneidentity.com, Chirag Zakhmi
One concern I have with the User Scripts API, however, is that while it allows the execution of scripts, it doesn't provide a straightforward way to retrieve the returned value from the executed script. In our workflow, it's essential to capture this returned value and pass it on to the next function. Do you have any insights or suggestions on how we could achieve this within the context of the User Scripts API?

Hi Sumit,

The proposal I shared does include an InjectionResult callback. I think that should be enough to help with your use case?

Thanks,

Oliver Dunk | DevRel, Chrome Extensions | https://developer.chrome.com/ | London, GB

Sumit Kumar

unread,
May 7, 2024, 5:06:21 AM5/7/24
to Chromium Extensions, Oliver Dunk, Chromium Extensions, Rahul Yadav, will.m...@oneidentity.com, Chirag Zakhmi, Sumit Kumar
Hi Oliver,
Definitely the proposal looks promising and I am looking forward to integrate it in our extension. But can you please provide any tentative date for the feature to be available in userScripts API. It will help us plan efficiently as this is the major blocker in the migration process for us.

Thanks,
Sumit

Oliver Dunk

unread,
May 7, 2024, 9:30:49 AM5/7/24
to Sumit Kumar, Chromium Extensions, Rahul Yadav, will.m...@oneidentity.com, Chirag Zakhmi, Sumit Kumar
I don't have a specific date to share I'm afraid - however this is at the top of the list of changes we want to work on.

I'd expect it would probably be on the order of a few months, if that helps for planning :)
Oliver Dunk | DevRel, Chrome Extensions | https://developer.chrome.com/ | London, GB

Reply all
Reply to author
Forward
0 new messages