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

Inject custom JS scripts #20

Open
muodov opened this issue Jul 5, 2022 · 5 comments
Open

Inject custom JS scripts #20

muodov opened this issue Jul 5, 2022 · 5 comments

Comments

@muodov
Copy link
Contributor

muodov commented Jul 5, 2022

Inject custom JS scripts

Submitter(s)

Maxim Tsoy, Duck Duck Go

Motivation

User scripts (aka content scripts) is a powerful tool that unlocks many possibilities such as

  • content customization (e.g. applying custom CSS, adding UI elements)
  • security and privacy protection (e.g. blocking harmful APIs, preventing data leakage and fingerprinting)
  • enriching web app functionality (e.g. filling previously saved passwords, translating text to foreign language, polyfilling missing APIs)

Injected scripts can also be a workaround when another WebView feature is not available: for example, due to the lack of granular cookie control in native WebView APIs, DuckDuckGo injects a script to augment document.cookie API.

There is a previously closed issue #10, which is similar, but here I try to expand the use case based on our experience.

Stakeholders

  • WebView vendors: Google (WebView), Microsoft (WebView2), Apple (WKWebView)
  • App developers that need customizations of the rendered content

Analysis

Web extensions have a similar concept of content scripts, however the features provided by the native WebView implementaions are much less versatile and not standardized.

Most platforms implement a basic evaluateJS(<string_payload>) kind of method, which allows to execute arbitrary JS code in the page context. However, it is limited, and lacks some important features that would make developers live easier if they were cross-platform:

1. Run scripts in isolated world

It is common for web pages to change JS prototypes and global variables. This can easily affect the scripts injected by the native app. This can lead to security and privacy issues, and is very hard to prevent. Isolated world prevents these collisions by running the content script within its own JS environment, while still allowing it to access the DOM. Moreover, scripts in isolated world are not affected by CSP and other restrictions imposed on the page scripts.
To be clear, isolated world is not a replacement to the main world.

  • Isolated world (called "content world" there) is available on Apple platforms; Android and Windows WebViews can only execute code in the page context ("main" world)

2. Inject scripts in all iframes, including cross-origin and sandboxed ones.

This is currently a serious limitation on Android, which only allows executing in same-origin contexts. For DuckDuckGo browsers, this makes it very difficult to apply tracking protections, since trackers often operate in a third-party context.

3. Inject scripts before all page scripts.

Web extensions have a "run_at" paramenter that controls when the script will be executed. This is crucial for any security and privacy customizations that need to apply protections before any malicious script can take effect. For example, DDG anti-fingerprinting protection augments the APIs, but it only protects from scripts executed after it.

  • WKWebView and WebView2 can do this (although API approaches are different), but Android WebView doesn't allow it

4. Secure messaging between the native code and injected scripts.

Content scripts often work in combination with the native components and so require communication. For example, in DuckDuckGo browsers scripts use this to read user configuration (which is managed by the native app), and trigger native UI changes on page-generated events.

  • WKWebView provides a convenient API for passing async messages, but Android WebView and Windows WebView2 do not have an alternative. It is possible to achieve a one-way communication by exposing global JS callables, but without isolated world (see above) this is insecure, since page scripts would be able to use those globals too.

5. Inject scripts in ServiceWorkers and (Shared) Web Workers.

Some scripts are designed to change the JS environment of the page scripts. For example, DDG cookie protection deliberately changes the document.cookie API to protect against long-lived tracking cookies. However, there is currently no (straightforward) way to do this in Workers, which have access to powerful APIs as well (e.g. Cookie Store API)

  • to my knowledge, this is currently not possible on any platform

Related W3C deliverables and/or work items

WebExtensions CG

How is the issue solved in the Browser, and what’s more is needed?

In browsers, many of these issues are solved by Web Extension API. I think a lot of design patterns could be (and already are) borrowed from there. WKUserScript is clearly inspired by, and probably built upon the same technology.

However, just exposing the WebExtensions API might not always be the right solution: WebViews are embedded in native apps, which operate and protect under a different security and performance model. In general, WebView should probably give more raw control than WebExtensions API.

@muodov
Copy link
Contributor Author

muodov commented Jul 8, 2022

We discussed this during the meeting on Jul 6th. At DDG, we hit quite a few limitations on different platforms, Apple providing the most features and Android having the most hard limits so far.

Injecting scripts is a powerful tool that can solve many different problems, and most of the underlying technology is already there for web extensions. My view is that ideally WebViews should expose more low-level and powerful modification features than browser extensions, since it is operating on the OS-native side of things. Besides, on Android and Windows there are alternative WebView implementations that can be more or less restrictive.

@rayankans would you mind sharing some perspective from the Chrome side? DuckDuckGo is struggling with some WebView limitations on Android, and it would be great to hear your opinion.

@rayankans
Copy link
Contributor

Thanks @muodov, agreed that having more control over the injection site would be useful.

The issue I brought up during the meeting was more on the API access level, not the API itself. I believe powerful APIs like JS injection should only be available for 1P web content, or failing that be gated by some form of user permission. That is however a separate issue altogether.

I was also wondering about the second point you made (injecting in cross-origin scripts). This seems somewhat related to issue #16 about blocking certain 3P scripts. Would that be enough or is there a use-case for being able to inject in cross-origin contexts?

@muodov
Copy link
Contributor Author

muodov commented Jul 13, 2022

@rayankans If I understood it correctly, #16 boils down to distinguishing injected scripts from the page scripts, and blocking based on that distinction.
The second point of this issue is about being able to inject scripts at all. Indeed, our use-case requires injecting scripts in cross-origin iframes. Concrete examples from the top of my head:

  • changing DOM: e.g. autofill UI from password managers, or annotating selected text as in Google Translate extension. This use-case would also benefit from isolated context (point 1)
  • applying extra security/privacy protections: they often need to change JS globals in the page world to intercept and block complex harmful scenarios from page scripts

These use-cases should not be bound by SOP, because these script injections don't originate from potentially untrusted web content, but from the user agent itself - the native app using WebView. Web extensions provide these possibilities even though they are just plugins to a user agent, but in case of WebView the embedding app acts as the user agent itself, and it seems strange that it's more limited in terms of content manipulation. Webkit exposes web extension abstractions for some time now, so we could probably look into how they address possible security concerns in this model.

QingAn added a commit that referenced this issue Jul 27, 2022
Merge use case of #20
@QingAn
Copy link
Contributor

QingAn commented Jul 27, 2022

@muodov Please take a look if PR #26 works for you.

@aluhrs13
Copy link

aluhrs13 commented Aug 8, 2022

Noting something I mentioned in the CG meeting last month - WebView2 on Windows doesn't treat 1P or 3P JS differently for this use-case, in a similar vein to what @muodov mentioned around originating from the user-agent. Totally possible that more locked-down mobile platforms have a different boundary, but for us if you're running a native app you could modify much of this in other ways (Fiddler being the easy example).

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

No branches or pull requests

4 participants