GoogleChrome / chrome-extensions-samples

Chrome Extensions Samples
https://developer.chrome.com/docs/extensions
Apache License 2.0
15.37k stars 8.19k forks source link

Sample: Transfer a blob from a background context to a page #766

Open dotproto opened 1 year ago

dotproto commented 1 year ago

Goal of the demo

Demonstrate how an extension can create a blob in the extension's service worker and transfer it to a page's main world.

Suggested implementation

Sketch of my initial implementation plan

  1. Have a content script inject an iframe into a page
  2. Run a script in the iframe to post a message back to the service worker using navigator.serviceWorker.controller.postMessage()
  3. In the SW's message handler, generate a blob OR use structuredClone() to create a copy of a blob.
  4. Use event.source.postMessage(msg, [transferable]) to reply to the client and transfer the blob
  5. In the iframe's message handler, use window.parent.postMessage(msg, "<origin>", [transferable]) to transfer the data to the page

Related links

Notes

Initial findings suggest that it's not possible to transfer variables across origins. I'm tentatively thinking that to work around this, we can use URL.createObjectURL() in the iframe and directly reference the object URL somewhere that's easily visible to the end user (e.g. canvas, Audio/Video element, etc.).

tophf commented 1 year ago

Well, I can physically make such a site myself, so I physically can cite a URL. It's just meaningless because it won't change the fact that it's physically possible. There are sites who don't like extensions e.g. those that can help the visitors cheat if it's a game or a test or something more sensitive, so they do have an incentive to do extra stuff to fight extensions.

As for the sites dig, I guess you're trying to make a joke about my imprecise usage of the term "site", but it's widely used (I preemptively decline a citation request) to denote the contents of a local browser's tab, so naturally it can remove anything from the tab's DOM where the web page of the web site is shown.

guest271314 commented 1 year ago

Well, I can physically make such a site myself, so I physically can cite a URL.

No, you can't.

You will need to demonstrate how you will make a Web site which will remove the <iframe> that I append to the document I am viewing in my browser on my machine.

guest271314 commented 1 year ago

Well, I can physically make such a site myself, so I physically can cite a URL.

Further, per your claim the Web site can remove any code whatsoever, HTML, CSS, JavaScript, etc., whatever you consider to be a "secure" implementation of communication - including chrome itself

chrome = null;

Now what do you do?

tophf commented 1 year ago

No, you can't.

I can. You seem to keep milking the joke about differentiating remote sites and their local rendition in a browser tab. Here's the simplest demo of such a web site html:

<script>addEventListener('DOMNodeInserted', e => e.target.remove())</script>

A more "advanced" version would also remove the stuff added by a content script at document_start:

<script>
document.documentElement.querySelectorAll('*').forEach(el => {
  if (el !== document.head) el.remove();
});
addEventListener('DOMNodeInserted', e => e.target.remove());
</script>

Next the site can show its own elements and keep track of them in an internal WeakSet.

chrome = null; Now what do you do?

Assuming it's in the web page's MAIN world, it doesn't affect the isolated world of the content scripts.

guest271314 commented 1 year ago

Well, I can just remove that code.

tophf commented 1 year ago

It will be obfuscated in the real web page and it'll contain the main functionality of the page, so removing it is not an option. What an extension can do is to preemptively cloak the various DOM methods.

guest271314 commented 1 year ago

It will be obfuscated in the real web page and it'll contain the main functionality of the page, so removing it is not an option. What an extension can do is to preemptively cloak the various DOM methods.

No it won't.

There is no obfuscation for hackers.

tophf commented 1 year ago

It will be interspersed with the real code of the web page that has already been executed, so removing the script element after the fact won't help. Cloaking the DOM methods would help, but there's a lot to cover, especially if we include the possibility of a web site using a fresh src-less iframe to extract the original un-cloaked methods and .call them on the main document. Anyway, I agree it's not something most extensions need to worry about.

guest271314 commented 1 year ago

I think it is important to point out while the process I use to stream data does work, it could be written out to avoid using an iframe, and be ergonomic and overt to the developer.

Technically you can just fetch() whatever data you want using fetch('chrome-extension://xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/path/to/resource') or import('chrome-extension://xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/path/to/resource') on any arbitrary Web site to avoid iframe altogether with "web_accessible_resources". The only site where I have had issues doing that is Twitter.

That is why the use-case(s) description must be detailed and precise, so Chromium authors can't be ambiguous in their specification language and implementation should we get that far.

guest271314 commented 1 year ago

You have to be real pecise when dealing with technical documents. E.g., Chrome claims to capture "system audio" for getDisplayMedia(), yet there are exceptions which I requested be disclosed to the developers at large, yet the reply was a "small percentage" of users would even be aware of speechSynthesis https://github.com/GoogleChrome/developer.chrome.com/pull/3947 - until you try to capture speechSynthesis.speak() yourself for a requirement.

So, I would be very technical about exactly what is being requested here for a feature - with use cases and non-goals clearly spelled out so confusion as to scope cannot rationally be claimed.

dotproto commented 1 year ago

Dang, y'all got into quite a bit of a discussion. I don't have time to catch up on it right now, but I swung by to add some notes and figured I should address one point that stood out in passing.

Goal of the demo transfer it to a page's main world

It's the wrong goal in the context of the linked issue.

I was initially aiming to tackle what I expected to be the more complicated version of the same basic use case; I figured if we could solve for passing a Blob to a main world content script, then we'd probably also solve for passing a Blob to an isolated world content script as well.

Unfortunately, my initial experiments seem to indicate that transferables cannot be sent across origin. At the moment my planned work around is to create object URL in the extension iframe on the page. That may change after I read through the full discussion in the comments here.

guest271314 commented 1 year ago

my initial experiments seem to indicate that transferables cannot be sent across origin

Yes they can be, if we are talking about from chrome://<id> to arbitrary Web page, and vice versa.

guest271314 commented 1 year ago

@tophf

Reviewing my own approach, I don;t think we have to keep the iframe in the Web page once we transfer the ReadableStream to the Web page. So the iframe would only serve to transfer the ReadableStream to the Web page then we can remove it ourselves, which should take 1 second or so.

I'll test later today or tomorrow.

tophf commented 1 year ago

I would just transfer port2 of a MessageChannel as you already suggested, so that it can be also used for future communication.

guest271314 commented 1 year ago

@tophf Is there any reason you don't use "externally_connectable"?

tophf commented 1 year ago

The linked issue is about an entirely different use case: a content script. Content scripts run in an isolated world which cannot be spoofed or intercepted or broken by the web page via getters/setters on various global types and prototypes.

dotproto commented 1 year ago

my initial experiments seem to indicate that transferables cannot be sent across origin

Yes they can be, if we are talking about from chrome://<id> to arbitrary Web page, and vice versa.

Thanks for the clarification. I was experimenting with websites, not extension contexts. I should have known better 😅

guest271314 commented 1 year ago

The linked issue is about an entirely different use case: a content script. Content scripts run in an isolated world which cannot be spoofed or intercepted or broken by the web page via getters/setters on various global types and prototypes.

Doesn't really matter. You can stream data using message passing. There is nothing special about a Blob. JSON can be streamed. Does "content script" exclude chrome.scripting.executeScript()?

tophf commented 1 year ago

You asked about externally_connectable, which is about the opposite task: collaborating with the main world of the page.

guest271314 commented 1 year ago

I am trying to understand exactly what you are trying to achieve that you can't now.

In the content script you can just do

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  // do stuff
});

in the ServiceWorker

chrome.action.onClicked.addListener(async (tab) => {
  const [{ id }] = await chrome.tabs.query({
                           active: true,
                           currentWindow: true,
                        });
  chrome.tabs.sendMessage(id, { name: 'blob' }, async (message) => {
    console.log(message);
  });    
});

We can serialize data of you really have a Blob or TypedArray using TextEncoder() and deserialize with TextDecoder().

tophf commented 1 year ago

The linked issue is about transferring huge amounts of data "instantly", i.e. in a matter of a few milliseconds, while staying wholly within the isolated world so that nothing in the main world can see the data or break the communication.

It means that current chrome messaging is immediately out of the question because it would take like a minute to transfer 1GB of JSON-compatible binary data on an average not-so-fast notebook.

The workaround via a web_accessible_resources iframe is practically acceptable for most extensions that can accept the theoretical risks of being detected/broken from the main world and the added 50+ms overhead to create the iframe environment. To clarify "detection": assuming we use a closed ShadowDOM the web page can only detect that an empty element was added, whereas a content script of another extension can peek into the closed ShadowDOM and see there's an iframe (it won't be able to peek into iframe though without resorting to chrome.debugger API which is an entirely different story).

guest271314 commented 1 year ago

The linked issue is about transferring huge amounts of data "instantly", i.e. in a matter of a few milliseconds, while staying wholly within the isolated world so that nothing in the main world can see the data or break the communication.

It means that current chrome messaging is immediately out of the question because it would take like a minute to transfer 1GB of JSON-compatible binary data on an average not-so-fast notebook.

Are you sure you tested that?

What data are you transferring from the ServiceWorker to the content script where the transfer needs to be 1GB or more in under 1 second and instantaneous?

What is the use case?

I can stream real-time audio using data URL's or JSON with onMessage. Streaming audio ain't easy. It is easy to detech glitches, gaps in playback.

What I would do is transfer a FileSystemFileHandle to the ServiceWorker, write to the file, then either close the stream or transfer the FileSystemFileHandle back to the content script, call getFile() then call arrayBuffer() on the File object.

Or transfer a WebAssembly.Memory object and write and read essentially in parallel.

I am not sure why the context restriction is content scripts, which are inherently unreliable.

For more reliability you can just open the document listed in "web_accessible_resources" as a Tab, then use fetch(), intercept the request and send data back using respondWith().

The

on an average not-so-fast notebook

criteria will remain the same using any approach.

guest271314 commented 1 year ago

@dotproto

Changing the name to "Sample: Transfer a blob from a background context to a page" is still not techically accurate, because Blobs are not transferable objects. The last time I checked Chrome still had not implemented Blob type for WebRTC data channels.

The sample request could be solved at once by various means:

  1. Use the origins set, if any, in "web_accessible_resources" and apply those to allowlist BroadcastChannel from chrome-extension:// origin;
  2. Use the origins set, if any, in "web_accessible_resources" and apply those to allowlist the content script as a WindowClient of MV3 ServiceWorker

Either will provide a means to communicate between the content script/Web page and the ServiceWorker.

A content script as WindowClient will alos allow intercepting fetch() requests so we can implement half-duplex streaming.

The BroadcastChannel solution appears to be the simplest to implement in extension source code, as ServiceWorker was not designed for extensions.

guest271314 commented 1 year ago

The linked issue is about transferring huge amounts of data "instantly", i.e. in a matter of a few milliseconds, while staying wholly within the isolated world so that nothing in the main world can see the data or break the communication. The workaround via a web_accessible_resources iframe is practically acceptable for most extensions that can accept the theoretical risks of being detected/broken from the main world and the added 50+ms overhead to create the iframe environment. To clarify "detection": assuming we use a closed ShadowDOM the web page can only detect that an empty element was added, whereas a content script of another extension can peek into the closed ShadowDOM and see there's an iframe (it won't be able to peek into iframe though without resorting to chrome.debugger API which is an entirely different story).

There is nothing in developer.chrome that specifies a means to transfer data between MV3 ServiceWorker and content script, or Web page contexts.

Blobs are not transferable.

So this issue amounts to a feature request.

If the concern is an arbitrary Web page running code continuously to check check and see if an iframe is appended to the document, which I have not observed in the wild whatsoever, you can open a new window, which the Web page has no control over observing or closing and achieve the same transfer of data between iframe and Web page.

Something like I do here https://github.com/guest271314/sw-extension-audio/blob/main/background.js

const createAudioWindow = async (notification = false) => {
  let url = chrome.runtime.getURL('audio.html');
  if (notification) {
    url += '?notification=1';
  }
  console.log(url);
  ({ id } = await chrome.windows.create({
    type: 'popup',
    focused: false,
    top: 1,
    left: 1,
    height: 1,
    width: 1,
    url,
  }));
  await chrome.windows.update(id, { focused: false });
  return id;
};

Now you can communicate using opener.postMessage() from the window and onmessage in the Web page, then close the window when done with the transfer.

guest271314 commented 1 year ago

Evidently setSelfAsOpener option of chrome.windows.create() doesn't work with ServiceWorker, even though it is not documented to throw.

Per this https://stackoverflow.com/a/72124984 Chrome maximum ArrayBuffer size is 2145386496 (1.998 GB).

new ArrayBuffer(1072693248)

half of the maximum value per that test crashed the window on *nix.

WebAssembly.Memory can grow(), the last time I checked was limited as well, though there was activity to increase to 4GB https://v8.dev/blog/4gb-wasm-memory, https://github.com/WebAssembly/spec/issues/1116.

new Array(536346624)

.499 GB did not throw using the following approach; was not instant. You should be able to run the test linked above between 536346624 and 1072693248 to determine when Chrome or Chromium will crash on your machine. The sole purpose is to transfer data from ServiceWorker to content script (or Web page) then close the window

manifest.json

{
  "name": "ServiceWorker => ArrayBuffer => Content script",
  "version": "1.0",
  "description": "Transfer data from ServiceWorker to content script",
  "manifest_version": 3,
  "background": {
    "service_worker": "background.js",
    "type": "module"
  },
  "content_scripts": [
    {
      "matches": [
        "<all_urls>"
      ],
      "js": [
        "contentscript.js"
      ],
      "run_at": "document_start"
    }
  ],
  "host_permissions": [
    "http://*/*",
    "https://*/*"
  ],
  "permissions": [
    "activeTab",
    "tabs",
    "windows"
  ],
  "web_accessible_resources": [{
      "resources": ["*.html", "*.js"],
      "matches": ["<all_urls>"],
      "extensions": []
  }]
}

background.js

const bc = new BroadcastChannel('transfer');
const buffer = new ArrayBuffer(536346624); 
bc.onmessage = async (e) => {
  console.log(e);
  const transfer = structuredClone(buffer, {transfer: [buffer]});
  bc.postMessage(transfer, [transfer]);
  console.assert(buffer.byteLength === 0, {buffer});
}

chrome.action.onClicked.addListener(async (tab) => {
  chrome.tabs.sendMessage(tab.id, { url: chrome.runtime.getURL('index.html') });    
});

contentscript.js

{
  chrome.runtime.onMessage.addListener((message) => {
    const handleMessage = (e) => {
      if (e.origin === new URL(message.url).origin) {
        console.log(e.data);
        removeEventListener('message', handleMessage);
      }
    }
    addEventListener('message', handleMessage);
    window.open(message.url, location.href, 'menubar=no,location=no,resizable=no,scrollbars=no,status=no,width=50,height=50');
  });
}

index.html

<script src="./script.js"></script>

script.js

const bc = new BroadcastChannel('transfer');
bc.onmessage = (e) => {
  opener.postMessage(e.data, name, [e.data]);
  bc.close();
  close();
}
bc.postMessage(null);
tophf commented 1 year ago

The linked issue is indeed a feature request to implement the means to transfer huge data "instantly". I've edited it to avoid the fixation on blobs as it's not about blobs in particular, but any binary data.

I've retested the actual time:

There are many use cases where huge binary data needs to be sent "instantly", for which chrome sendMessage is 10-100 times slower because it applies JSON.stringify that escapes some characters and then Chrome internally encodes the result to UTF-8 again in its Mojo IPC, so anything non-ASCII you provide to sendMessage, including text you just manually encoded to UTF-8 or any other at least 8-bit encoding, will be re-encoded slowly again. Then the reverse will be applied in the receiver.

It means that chrome sendMessage stresses the CPU excessively, which can be observed in profiler and task manager. It blocks the extension process and the receiver process and sometimes the browser process as well, introducing lags and jank.

I am not sure why the context restriction is content scripts, which are inherently unreliable.

It's the opposite. Anything outside the content script's isolated world is unreliable, i.e. the main world of a web page where any prototype or a standard global type can be spoofed (maliciously) or broken (inadvertently e.g. by a weird polyfill). The content script is the only reliable script an extension can use in a web page, although of course it's less reliable than the environment of its own origin iframe, but that's an entirely different cross-origin page environment.

guest271314 commented 1 year ago

How did you send a Blob? A Blob is not transferable. None of these transfers occur "instantly".

I was comparing chrome.scripting.executeScript() to a content script.

You have not described a use case where speed of transfer is important or required.

You cannot spoof, intercept of otherwise do anything about the content script or Web page itself opening a new window.

tophf commented 1 year ago

I've used different terms specifically to indicate the difference between sending and transferring. Sending is port.postMessage(blob), transferring is port.postMessage(buf, [buf]). My measurements for sending a blob in the comment above were incorrect as I've included the time to fill the blob with random values. The actual time is less than 1ms i.e. practically instantaneous. This was what confused me the first time I investigated transferability of blobs. Apparently blobs are using a shared repository, you've mentioned something like that, so when we send a blob only the handle is actually sent. Time to transfer a 500MB buffer was correct, it's 0.5sec;

chrome.scripting.executeScript() injects a content script by default unless you specify world: 'MAIN' and the injected script becomes a "page script", not a "content script". I name it a "page script" because it runs in the JS environment of the page, so it plays by the same rules, the only difference being that it's not subjected to the CSP of the page.

Speed of transfer is important or required depending on the amount of data and whether sending it introduces lags or stresses the CPU so much that its fan becomes audible on a low-power device like notebooks. Most extensions don't need this feature. In your case the difference might be negligible, otherwise you would have probably noticed these problems.

Opening a new window is a viable workaround, however depending on the desktop environment and its settings it may introduce noticeable movement in some area of the screen and it may be also closed by another extension before the script in the window initializes.

guest271314 commented 1 year ago

Speed of transfer is important or required depending on the amount of data and whether sending it introduces lags or stresses the CPU so much that its fan becomes audible on a low-power device like notebooks.

I am still not certain what use case onMessage doesn't work with.

Why would we need to transfer 500MB of data to the content script?

If the use case is streaming media, onMessage works.

If the use case is downloading files we use chrome.downloads or File System Access API on the main thread - or we can transfer the FileSystemFileHandle to the ServiceWorker and write to the user file system there.

If you really want real-time communication you can use Native Messaging with libdatachannel as part of the host, so you can create a data channel connection from the content script.

guest271314 commented 1 year ago

Opening a new window is a viable workaround, however depending on the desktop environment and its settings it may introduce noticeable movement in some area of the screen and it may be also closed by another extension before the script in the window initializes.

The idea is not to occlude the window, rather make the window obvious so the user knows that window is only being used to transfer data to the content script.

Another extension can close any window or tab at any time, anyway.

guest271314 commented 1 year ago

Time to transfer a 500MB buffer was correct, it's 0.5sec;

Where does that data come from?

You can just use fetch('chrome-extension:/path/to/file') in the content script, then you have the speed of Fetch and ReadableStream as response which you can read while the stream is still in progress.

guest271314 commented 1 year ago

I think we can write the data directly with executeScript() to avoid having to transfer data

chrome.action.onClicked.addListener(async (tab) => {
  const [{ result }] = await chrome.scripting.executeScript({
    target: {
      tabId: tab.id,
    },
    world: 'ISOLATED',
    func: () => { 
      const buffer = new ArrayBuffer(2145386496 / 2); 
      console.log(buffer); 
      return buffer.byteLength;
    }
  });

  console.log(result);

});
tophf commented 1 year ago

Only func's code is sent as a string to the tab where a new function is created from this text, not the data. You can send the data via args parameter but internally that's the same as sendMessage because it also uses JSON.stringify + IPC. This API is just a polyfill for MV2 executeScript with code: `(${func})(${JSON.stringify(args).slice(1,-1)}).

I found a workaround that may be unbreakable:

  1. create an offscreen document in SW
  2. create an iframe there with the URL (or just the origin) of the web page in the tab
  3. call window.stop() in your content script that runs in that iframe at document_start, it's temporarily registered for the duration of the whole thing using chrome.scripting API, it should check location.ancestorOrigins to see whether it's embedded inside an extension document.
  4. SW sends a random id to the content script in the tab and to the offscreen iframe's content script
  5. these content scripts open BroadcastChannel using this id as a name
  6. the offscreen iframe sends a Blob, which takes less than 1 millisecond

It may need setting up declarativeNetRequest rule to strip X-Frame-Options:DENY and another rule to strip referer with the extension's id of the embedder origin if it's sent.

Currently I don't know how to obtain the window of the tab via this approach in order to be able to transfer stuff like a MessagePort. Might be impossible.

Another disadvantage is a possibly long time to open a slow server URL.

guest271314 commented 1 year ago

Only func's code is sent as a string to the tab where a new function is created from this text, not the data.

What difference does it make?

  1. create an offscreen document in SW

What is that? Something Chromium extension authors have been talking about?

Currently I don't know how to obtain the window of the tab via this approach

Not sure what you mean.

tophf commented 1 year ago

The difference is that your code doesn't send any data, it just sends func.toString().

guest271314 commented 1 year ago

That is not observble. You get the same data.

guest271314 commented 1 year ago

What does the data in the Blob consist of? What is the use case?

tophf commented 1 year ago

No, you don't get any data at all. The data is not a part of the function's code string.

tophf commented 1 year ago

The data is any binary generated or downloaded stuff. It is not a part of any function.

guest271314 commented 1 year ago

It doesn't matter. In the clicked window all you know is there is you Blob,

tophf commented 1 year ago

It won't contain data from SW, it's a useless exercise in semantics.

guest271314 commented 1 year ago

I don't think it matters. You create the data structure you want to be exposed in the window in the ServiceWorker. The clicked window gets that data.

So far you have not described exactly what the data consists of, and why it is important to transfer the data.

guest271314 commented 1 year ago

Is there any reason you don't just use fetch('chrome-extension://xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx') from the arbitrary window to get the data?

guest271314 commented 1 year ago

I was giving the idea of an arbitrary Web site removing an iframe from their site yesterday.

If that happens once, then you can simply remove that code from the site after your initial test.

Given the challenge of writing source code that removes any arbitrary iframe from a Web site and prevents said source code from being removed, such a requirement would be essentially impossible.

guest271314 commented 1 year ago

If you already have the data that needs to be transferred you can just set "web_accessible_resources" in the manifest.json then use fetch('chrome-extension://xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/path/to/file') or import json from 'chrome-extension://xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/path/to/file' assert { type: "json" } on the Web site itself.

tophf commented 1 year ago

We're going in circles for like 100th time, but it's fun so let's do it once again.

A web page can trivially delete any element immediately once it's added as I've shown using DOMNodeInserted event. It can be done using MutationObserver too, as well as using a periodic check, it doesn't matter which one as they all are trivial to implement. This code can't be removed automatically from an unknown web page not because it specifically resists removal but because such code will be an integral part of a large minified bundle, possibly with obfuscation - you'd have to analyze it manually. Such code can be added by another extension as well, not that it's likely, but still possible and trivial to implement. It is trivial, there's no challenge in writing it. Of course there's a way for an extension to protect the iframe: it can override all DOM methods that can be used to remove an element or to rewrite/reload the page or to extract such methods from a new src-less iframe.

executeScript fetch import

All of these are for static data included with the extension, it's not what the linked issue is about. The issue is about data in SW context (and nowhere else within the extension). It can be generated by JS or downloaded from an external URL or captured from desktop. It's not a part of the func's code or of the extension package.

guest271314 commented 1 year ago

This code can't be removed automatically from an unknown web page not because it specifically resists removal but because such code will be an integral part of a large minified bundle, possibly with obfuscation - you'd have to analyze it manually.

The code can be removed. If need be we can use Local Modifications to run the local code of our choosing.

The issue is about data in SW context (and nowhere else within the extension). It can be generated by JS or downloaded from an external URL or captured from desktop. It's not a part of the func's code or of the extension package.

However the code is generated it can be written to a local file and fetched with fetch() or import(), or import.

guest271314 commented 1 year ago

you'd have to analyze it manually.

Yes, one time. At the initial visit to any Web site that happens to runs such code, which I have never encountered in the field.

Thereafter we don't have that issue ever again.

All of these are for static data included with the extension, it's not what the linked issue is about. The issue is about data in SW context (and nowhere else within the extension). It can be generated by JS or downloaded from an external URL or captured from desktop. It's not a part of the func's code or of the extension package.

The download and capture from desktop use cases can be excluded altogether.

If we are really downloading content in the ServiceWorker we don't need to transfer that data to the content script at all. Why would we do that? We just write the data directly to the user file system with FileSystemFileHandle. If we are capturing content from the desktop, again, why would be send that data to the ServiceWorker then back to the content script?

A web page can trivially delete any element immediately once it's added as I've shown using DOMNodeInserted event. It can be done using MutationObserver too, as well as using a periodic check, it doesn't matter which one as they all are trivial to implement. This code can't be removed automatically from an unknown web page not because it specifically resists removal but because such code will be an integral part of a large minified bundle, possibly with obfuscation - you'd have to analyze it manually. Such code can be added by another extension as well, not that it's likely, but still possible and trivial to implement. It is trivial, there's no challenge in writing it.

I really don't think you can write such code that cannot be removed. In absense of an actual proof-of-concept of such code working in the field I don;t think you have demonstrated the feasibility of someone even trying to do that.

tophf commented 1 year ago

Although Local Modifications is not a part of the extensions API/platform, but indeed its functionality can be replicated via chrome.debugger API, and indeed it can be used to remove the code once we identified it manually. I didn't contest this part. My point is that the analysis for such code can't be reliably automated. Also, chrome.debugger is not an API a typical user would want to allow for an extension that's not related to debugging.

Writing to a local file inside the installed extension directory is indeed a possibility but in addition to being slower than sending a Blob directly between contexts it would require either installing the extension inside the default downloads directory or to use a nativeMessaging app to write the file into the directory of an installed extension, finding which reliably and automatically is not always trivial since the browser may be launched with --user-data-dir parameter.

guest271314 commented 1 year ago

@tophf I am curious what your proposed solution to this is?