[Extensions] Match and dispatch webRequest listeners in JS [chromium/src : main]

0 views
Skip to first unread message

woxxom (Gerrit)

unread,
Aug 8, 2026, 3:55:12 AM (8 days ago) Aug 8
to Andrea Orru, chromium...@chromium.org, chromium-a...@chromium.org, extension...@chromium.org
Attention needed from Andrea Orru

woxxom added 6 comments

Patchset-level comments
File-level comment, Patchset 2 (Latest):
woxxom . unresolved

FWIW the entire extensions js shim isn't immune to prototype hijacks which can be fixed using a few simple tweaks. I've listed several examples here. This can be also codified via eslint's `no-restricted-syntax` rule for future-proofing.

File extensions/renderer/resources/web_request_event.js
Line 102, Patchset 2 (Latest): $Array.push(result, headers[i]);
woxxom . unresolved

$Array.push will trigger a setter on Array.prototype for indexes. The only safe push method is, AFAIK, `Object.defineProperty(arr, arr.length, {__proto__: null, value: v, configurable: true, enumerable: true, writable: true})` (note that the options object is proto-less too to avoid hijacking of non-specified properties via Object.prototype)

Line 120, Patchset 2 (Latest): var leafCopy = $Object.assign({}, certificates[0]);
woxxom . unresolved

This will trigger setters on Object.prototype. Use `{...certificates[0]}` which clones the property descriptors.

Line 161, Patchset 2 (Latest): if (copy.requestHeaders) {
woxxom . unresolved

This will trigger Object.prototype getter. The solution is to either use `$Object.hasOwnProperty` or null the prototype `var copy={__proto__:null, ...details}` and optionally restore it at the end via `$Object.setPrototypeOf(copy, $Object.getPrototypeOf({}))`

Line 271, Patchset 2 (Latest): $Function.apply(entry.callback, null, [listenerDetails, handledCallback]);
woxxom . unresolved

Can be simplified using the standard method used by webpack and others: `(0,entry.callback)(listenerDetails, handledCallback)`

Line 324, Patchset 2 (Latest): var entry = listenersById[matchingIds[i]];
woxxom . unresolved

This will trigger a getter on Array.prototype on out-of-bounds. To avoid it, check matchingIds[i]<listenersById.length

Open in Gerrit

Related details

Attention is currently required from:
  • Andrea Orru
Submit Requirements:
  • requirement satisfiedCode-Coverage
  • requirement satisfiedCode-Owners
  • requirement is not satisfiedCode-Review
  • requirement is not satisfiedNo-Unresolved-Comments
  • requirement is not satisfiedReview-Enforcement
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: comment
Gerrit-Project: chromium/src
Gerrit-Branch: main
Gerrit-Change-Id: I40d928c00a7cd26020d561baef027abfc88e550c
Gerrit-Change-Number: 8228125
Gerrit-PatchSet: 2
Gerrit-Owner: Andrea Orru <andre...@chromium.org>
Gerrit-CC: woxxom <wox...@gmail.com>
Gerrit-Attention: Andrea Orru <andre...@chromium.org>
Gerrit-Comment-Date: Sat, 08 Aug 2026 07:54:49 +0000
Gerrit-HasComments: Yes
Gerrit-Has-Labels: No
satisfied_requirement
unsatisfied_requirement
open
diffy

Andrea Orru (Gerrit)

unread,
Aug 8, 2026, 4:48:37 PM (8 days ago) Aug 8
to woxxom, chromium...@chromium.org, chromium-a...@chromium.org, extension...@chromium.org
Attention needed from woxxom

Andrea Orru added 1 comment

Patchset-level comments
woxxom . unresolved

FWIW the entire extensions js shim isn't immune to prototype hijacks which can be fixed using a few simple tweaks. I've listed several examples here. This can be also codified via eslint's `no-restricted-syntax` rule for future-proofing.

Andrea Orru

Thank you for your comments! They are valid, and the eslint rule is a good tip.

I'm aware of prototype hijacks. As a general rule, if one of my CLs is tagged as `WIP`, it means it's not yet meant for review. It can be a quick and dirty AI-generated experiment, and/or a way to test a hypothesis against CQ tests. It's basically a scratchpad until it's `Active` and sent for review.

You are welcome to comment on them (I don't mind) but I often abandon them, delete them, or otherwise rework them, so you should keep that in mind.

Open in Gerrit

Related details

Attention is currently required from:
  • woxxom
Submit Requirements:
  • requirement satisfiedCode-Coverage
  • requirement satisfiedCode-Owners
  • requirement is not satisfiedCode-Review
  • requirement is not satisfiedNo-Unresolved-Comments
  • requirement is not satisfiedReview-Enforcement
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: comment
Gerrit-Project: chromium/src
Gerrit-Branch: main
Gerrit-Change-Id: I40d928c00a7cd26020d561baef027abfc88e550c
Gerrit-Change-Number: 8228125
Gerrit-PatchSet: 2
Gerrit-Owner: Andrea Orru <andre...@chromium.org>
Gerrit-CC: woxxom <wox...@gmail.com>
Gerrit-Attention: woxxom <wox...@gmail.com>
Gerrit-Comment-Date: Sat, 08 Aug 2026 20:48:22 +0000
Gerrit-HasComments: Yes
Gerrit-Has-Labels: No
Comment-In-Reply-To: woxxom <wox...@gmail.com>
satisfied_requirement
unsatisfied_requirement
open
diffy

Andrea Orru (Gerrit)

unread,
Aug 9, 2026, 1:43:32 AM (7 days ago) Aug 9
to woxxom, chromium...@chromium.org, chromium-a...@chromium.org, extension...@chromium.org
Attention needed from woxxom

Andrea Orru added 1 comment

File extensions/renderer/resources/web_request_event.js
Line 324, Patchset 2: var entry = listenersById[matchingIds[i]];
woxxom . resolved

This will trigger a getter on Array.prototype on out-of-bounds. To avoid it, check matchingIds[i]<listenersById.length

Andrea Orru

No out-of-bounds is possible here. Also, `listenersById` is not an array.

Open in Gerrit

Related details

Attention is currently required from:
  • woxxom
Submit Requirements:
  • requirement satisfiedCode-Coverage
  • requirement satisfiedCode-Owners
  • requirement is not satisfiedCode-Review
  • requirement is not satisfiedNo-Unresolved-Comments
  • requirement is not satisfiedReview-Enforcement
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: comment
Gerrit-Project: chromium/src
Gerrit-Branch: main
Gerrit-Change-Id: I40d928c00a7cd26020d561baef027abfc88e550c
Gerrit-Change-Number: 8228125
Gerrit-PatchSet: 3
Gerrit-Owner: Andrea Orru <andre...@chromium.org>
Gerrit-CC: woxxom <wox...@gmail.com>
Gerrit-Attention: woxxom <wox...@gmail.com>
Gerrit-Comment-Date: Sun, 09 Aug 2026 05:43:16 +0000
satisfied_requirement
unsatisfied_requirement
open
diffy

Andrea Orru (Gerrit)

unread,
Aug 14, 2026, 3:52:44 PM (2 days ago) Aug 14
to Devlin Cronin, chromium...@chromium.org, chromium-a...@chromium.org, extension...@chromium.org
Attention needed from Devlin Cronin

Andrea Orru voted Commit-Queue+1

Commit-Queue+1
Open in Gerrit

Related details

Attention is currently required from:
  • Devlin Cronin
Submit Requirements:
    • requirement satisfiedCode-Coverage
    • requirement satisfiedCode-Owners
    • requirement is not satisfiedCode-Review
    • requirement is not satisfiedReview-Enforcement
    Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
    Gerrit-MessageType: comment
    Gerrit-Project: chromium/src
    Gerrit-Branch: main
    Gerrit-Change-Id: I8aae3dd40ccdd12623855b4f546415ed1951df48
    Gerrit-Change-Number: 8260949
    Gerrit-PatchSet: 1
    Gerrit-Owner: Andrea Orru <andre...@chromium.org>
    Gerrit-Reviewer: Andrea Orru <andre...@chromium.org>
    Gerrit-Reviewer: Devlin Cronin <rdevlin...@chromium.org>
    Gerrit-Attention: Devlin Cronin <rdevlin...@chromium.org>
    Gerrit-Comment-Date: Fri, 14 Aug 2026 19:52:32 +0000
    Gerrit-HasComments: No
    Gerrit-Has-Labels: Yes
    satisfied_requirement
    unsatisfied_requirement
    open
    diffy

    Andrea Orru (Gerrit)

    unread,
    Aug 14, 2026, 4:25:20 PM (2 days ago) Aug 14
    to Chromium LUCI CQ, Devlin Cronin, chromium...@chromium.org, chromium-a...@chromium.org, extension...@chromium.org
    Attention needed from Devlin Cronin

    Andrea Orru added 1 comment

    File extensions/renderer/resources/web_request_event.js
    Line 257, Patchset 1 (Latest): // If the listener removed itself inside the callback,
    Andrea Orru . unresolved

    I will land a test for this edge case (we discussed it offline briefly).

    Open in Gerrit

    Related details

    Attention is currently required from:
    • Devlin Cronin
    Submit Requirements:
      • requirement satisfiedCode-Coverage
      • requirement satisfiedCode-Owners
      • requirement is not satisfiedCode-Review
      • requirement is not satisfiedNo-Unresolved-Comments
      • requirement is not satisfiedReview-Enforcement
      Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
      Gerrit-MessageType: comment
      Gerrit-Project: chromium/src
      Gerrit-Branch: main
      Gerrit-Change-Id: I8aae3dd40ccdd12623855b4f546415ed1951df48
      Gerrit-Change-Number: 8260949
      Gerrit-PatchSet: 1
      Gerrit-Owner: Andrea Orru <andre...@chromium.org>
      Gerrit-Reviewer: Andrea Orru <andre...@chromium.org>
      Gerrit-Reviewer: Devlin Cronin <rdevlin...@chromium.org>
      Gerrit-Attention: Devlin Cronin <rdevlin...@chromium.org>
      Gerrit-Comment-Date: Fri, 14 Aug 2026 20:25:04 +0000
      Gerrit-HasComments: Yes
      Gerrit-Has-Labels: No
      satisfied_requirement
      unsatisfied_requirement
      open
      diffy

      Devlin Cronin (Gerrit)

      unread,
      Aug 14, 2026, 6:36:25 PM (2 days ago) Aug 14
      to Andrea Orru, Chromium LUCI CQ, Devlin Cronin, chromium...@chromium.org, chromium-a...@chromium.org, extension...@chromium.org
      Attention needed from Andrea Orru

      Devlin Cronin added 6 comments

      Patchset-level comments
      Devlin Cronin . resolved

      Thanks, Andrea!

      File extensions/renderer/resources/web_request_event.js
      Line 232, Patchset 2 (Latest): const matchingIds = webRequestNatives.GetMatchingListeners(
      Devlin Cronin . unresolved

      do we need to set the prototype of the return value of GetMatchingListeners() to null in the C++ so that it can't be intercepted with getters to the index?

      Line 255, Patchset 2 (Latest): $Array.push(listener.blockedDispatches, dispatch);
      Devlin Cronin . unresolved

      couldn't we do this above the function.apply() so that it always gets added / removed from the blockedDispatches set and we don't have to worry about whether the listener removed itself in the callback?

      Line 245, Patchset 2 (Latest): try {
      let result =
      $Function.apply(listener.callback, null, [filteredDetails]);
      if (allowAsyncResponsesForAllEvents &&
      result instanceof $Promise.self) {
      // Blocking listeners can return a promise.
      if (trackedListeners[listener.id] === listener) {
      // Still registered. Track the dispatch so `removeListener()` can
      // decrement the block count if removed while the promise is
      // pending.
      $Array.push(listener.blockedDispatches, dispatch);
      } else {

      // If the listener removed itself inside the callback,
      // `removeListener()` already ran without seeing this dispatch.
      // Decrement the block count now with no response.
      decrementBlockCount(dispatch, undefined);
      }
      // Async rejections unblock the dispatch without recording an error
      // (the synchronous dispatch loop has completed); rethrowing
      // surfaces the rejection in the console.
      $Promise.catch($Promise.then(result, function(asyncResult) {
      onEventHandled(listener, dispatch, asyncResult);
      }), function(e) {
      onEventHandled(listener, dispatch, undefined);
      throw e;
      });
      } else {
      // Synchronous return value.
      decrementBlockCount(dispatch, result, listener.extraInfoSpec);
      }
      } catch (e) {
      // On synchronous failure, decrement the block count with no response
      // and save the error.
      recordError(dispatch, e);
      decrementBlockCount(dispatch, undefined);
      }
      Devlin Cronin . unresolved

      this is a chunky block -- do you think we should extract to a helper function? (runSyncBlockingListener or similar?)

      Line 301, Patchset 2 (Latest): // Rethrows the first listener exception, if any.
      Devlin Cronin . unresolved

      If I'm reading it correctly, we'd previously throw every listener error, not just the first. Is this a behavior change?

      Line 510, Patchset 2 (Latest): // Discards any pending asynchronous responses and decrements the block
      // count with no response for each waiting dispatch.
      const blockedDispatches = $Array.splice(listener.blockedDispatches, 0);
      for (let i = 0; i < blockedDispatches.length; ++i) {
      decrementBlockCount(blockedDispatches[i], undefined);
      }
      Devlin Cronin . unresolved

      hmm... is this the proper behavior? (Does this match the behavior today?)

      The listener received the event, so removing itself as a listener isn't *that* unreasonable, even if it still wants to handle the event it got:

      ```
      chrome.webRequest.onAuthRequired.addListener(
      async function listener(event, reply) {
      if (await isMyEvent(event)) {
      // Found our event; no need to listen for more.
      chrome.webRequest.onAuthRequired.removeListener(listener);
      getEventResponse(event).then(reply);
      return;
      }
      reply(undefined);
      });
      ```
      Open in Gerrit

      Related details

      Attention is currently required from:
      • Andrea Orru
      Submit Requirements:
      • requirement satisfiedCode-Coverage
      • requirement satisfiedCode-Owners
      • requirement is not satisfiedCode-Review
      • requirement is not satisfiedNo-Unresolved-Comments
      • requirement is not satisfiedReview-Enforcement
      Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
      Gerrit-MessageType: comment
      Gerrit-Project: chromium/src
      Gerrit-Branch: main
      Gerrit-Change-Id: I8aae3dd40ccdd12623855b4f546415ed1951df48
      Gerrit-Change-Number: 8260949
      Gerrit-PatchSet: 2
      Gerrit-Owner: Andrea Orru <andre...@chromium.org>
      Gerrit-Reviewer: Andrea Orru <andre...@chromium.org>
      Gerrit-Reviewer: Devlin Cronin <rdevlin...@chromium.org>
      Gerrit-Attention: Andrea Orru <andre...@chromium.org>
      Gerrit-Comment-Date: Fri, 14 Aug 2026 22:36:11 +0000
      Gerrit-HasComments: Yes
      Gerrit-Has-Labels: No
      satisfied_requirement
      unsatisfied_requirement
      open
      diffy

      Andrea Orru (Gerrit)

      unread,
      Aug 14, 2026, 8:04:20 PM (2 days ago) Aug 14
      to Chromium LUCI CQ, Devlin Cronin, chromium...@chromium.org, chromium-a...@chromium.org, extension...@chromium.org
      Attention needed from Devlin Cronin

      Andrea Orru added 5 comments

      File extensions/renderer/resources/web_request_event.js
      Line 255, Patchset 2: $Array.push(listener.blockedDispatches, dispatch);
      Devlin Cronin . resolved

      couldn't we do this above the function.apply() so that it always gets added / removed from the blockedDispatches set and we don't have to worry about whether the listener removed itself in the callback?

      Andrea Orru

      Ah, yes -- that's much, much cleaner. I made this change as an afterthought and didn't see there was a cleaner way.

      We can also move `dispatch.blockCount++` inside the helpers (both `runBlockingListener` and `runAsyncBlockingListener`).

      Line 257, Patchset 1: // If the listener removed itself inside the callback,
      Andrea Orru . resolved

      I will land a test for this edge case (we discussed it offline briefly).

      Andrea Orru

      Added a TODO.

      Devlin Cronin . resolved

      this is a chunky block -- do you think we should extract to a helper function? (runSyncBlockingListener or similar?)

      Andrea Orru

      Done

      Line 301, Patchset 2: // Rethrows the first listener exception, if any.
      Devlin Cronin . resolved

      If I'm reading it correctly, we'd previously throw every listener error, not just the first. Is this a behavior change?

      Andrea Orru

      You are right. Each sub event had its own `EventEmitter` doing try/catch, so this is a behavior change.

      It seems like the correct way to do it is to use `bindingUtil.handleException` to mirror the way `EventEmitter::DispatchSync` does it in the legacy path: https://crsrc.org/c/extensions/renderer/bindings/event_emitter.cc;drc=11d511606e9d75ff9149f7b10fddd5e6af55af4a;l=301

      This probably deserves a test to check that multiple throws are emitted in the same way across the two paths. So adding a TODO for it.

      Line 510, Patchset 2: // Discards any pending asynchronous responses and decrements the block

      // count with no response for each waiting dispatch.
      const blockedDispatches = $Array.splice(listener.blockedDispatches, 0);
      for (let i = 0; i < blockedDispatches.length; ++i) {
      decrementBlockCount(blockedDispatches[i], undefined);
      }
      Devlin Cronin . unresolved

      hmm... is this the proper behavior? (Does this match the behavior today?)

      The listener received the event, so removing itself as a listener isn't *that* unreasonable, even if it still wants to handle the event it got:

      ```
      chrome.webRequest.onAuthRequired.addListener(
      async function listener(event, reply) {
      if (await isMyEvent(event)) {
      // Found our event; no need to listen for more.
      chrome.webRequest.onAuthRequired.removeListener(listener);
      getEventResponse(event).then(reply);
      return;
      }
      reply(undefined);
      });
      ```
      Andrea Orru

      Yes, it matches today's behavior. I updated the comment to clarify it (+ reflect the other changes).

      `CleanUpForListener` decrements with no response:
      https://crsrc.org/c/extensions/browser/api/web_request/extension_web_request_event_router.cc;drc=2f59ca9d5a2454810abc62d5a235b4347c7e90c4;l=2671

      Then when you get to `OnEventHandled` there's no blocked request for that listener and it returns:
      https://crsrc.org/c/extensions/browser/api/web_request/extension_web_request_event_router.cc;drc=2f59ca9d5a2454810abc62d5a235b4347c7e90c4;l=1998

      I don't love it. Maybe we should change it (in the future?).

      Open in Gerrit

      Related details

      Attention is currently required from:
      • Devlin Cronin
      Submit Requirements:
      • requirement satisfiedCode-Coverage
      • requirement satisfiedCode-Owners
      • requirement is not satisfiedCode-Review
      • requirement is not satisfiedNo-Unresolved-Comments
      • requirement is not satisfiedReview-Enforcement
      Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
      Gerrit-MessageType: comment
      Gerrit-Project: chromium/src
      Gerrit-Branch: main
      Gerrit-Change-Id: I8aae3dd40ccdd12623855b4f546415ed1951df48
      Gerrit-Change-Number: 8260949
      Gerrit-PatchSet: 4
      Gerrit-Owner: Andrea Orru <andre...@chromium.org>
      Gerrit-Reviewer: Andrea Orru <andre...@chromium.org>
      Gerrit-Reviewer: Devlin Cronin <rdevlin...@chromium.org>
      Gerrit-Attention: Devlin Cronin <rdevlin...@chromium.org>
      Gerrit-Comment-Date: Sat, 15 Aug 2026 00:04:06 +0000
      Gerrit-HasComments: Yes
      Gerrit-Has-Labels: No
      Comment-In-Reply-To: Devlin Cronin <rdevlin...@chromium.org>
      Comment-In-Reply-To: Andrea Orru <andre...@chromium.org>
      satisfied_requirement
      unsatisfied_requirement
      open
      diffy

      Andrea Orru (Gerrit)

      unread,
      Aug 14, 2026, 8:35:17 PM (2 days ago) Aug 14
      to Chromium LUCI CQ, Devlin Cronin, chromium...@chromium.org, chromium-a...@chromium.org, extension...@chromium.org
      Attention needed from Devlin Cronin

      Andrea Orru added 2 comments

      File extensions/renderer/resources/web_request_event.js
      Line 232, Patchset 2: const matchingIds = webRequestNatives.GetMatchingListeners(
      Devlin Cronin . unresolved

      do we need to set the prototype of the return value of GetMatchingListeners() to null in the C++ so that it can't be intercepted with getters to the index?

      Andrea Orru

      It seems like we don't.

      https://crsrc.org/c/v8/src/api/api.cc;drc=2f59ca9d5a2454810abc62d5a235b4347c7e90c4;l=8231

      `v8::Array::New` allocates and writes the content directly without calling setters.
      It returns a dense array without holes (`PACKED_ELEMENTS`). In my understanding `(0, 1, ..., length - 1)` and the `.length` property itself are data properties.

      So the prototype chain is not involved.

      Line 510, Patchset 2: // Discards any pending asynchronous responses and decrements the block
      // count with no response for each waiting dispatch.
      const blockedDispatches = $Array.splice(listener.blockedDispatches, 0);
      for (let i = 0; i < blockedDispatches.length; ++i) {
      decrementBlockCount(blockedDispatches[i], undefined);
      }
      Devlin Cronin . unresolved

      hmm... is this the proper behavior? (Does this match the behavior today?)

      The listener received the event, so removing itself as a listener isn't *that* unreasonable, even if it still wants to handle the event it got:

      ```
      chrome.webRequest.onAuthRequired.addListener(
      async function listener(event, reply) {
      if (await isMyEvent(event)) {
      // Found our event; no need to listen for more.
      chrome.webRequest.onAuthRequired.removeListener(listener);
      getEventResponse(event).then(reply);
      return;
      }
      reply(undefined);
      });
      ```
      Andrea Orru

      Yes, it matches today's behavior. I updated the comment to clarify it (+ reflect the other changes).

      `CleanUpForListener` decrements with no response:
      https://crsrc.org/c/extensions/browser/api/web_request/extension_web_request_event_router.cc;drc=2f59ca9d5a2454810abc62d5a235b4347c7e90c4;l=2671

      Then when you get to `OnEventHandled` there's no blocked request for that listener and it returns:
      https://crsrc.org/c/extensions/browser/api/web_request/extension_web_request_event_router.cc;drc=2f59ca9d5a2454810abc62d5a235b4347c7e90c4;l=1998

      I don't love it. Maybe we should change it (in the future?).

      Andrea Orru

      Sorry, the second link is: https://crsrc.org/c/extensions/browser/api/web_request/extension_web_request_event_router.cc;drc=2f59ca9d5a2454810abc62d5a235b4347c7e90c4;l=1989

      Because the listener is gone. Either way the conclusion is the same (the response is ignored).

      Open in Gerrit

      Related details

      Attention is currently required from:
      • Devlin Cronin
      Submit Requirements:
      • requirement satisfiedCode-Coverage
      • requirement satisfiedCode-Owners
      • requirement is not satisfiedCode-Review
      • requirement is not satisfiedNo-Unresolved-Comments
      • requirement is not satisfiedReview-Enforcement
      Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
      Gerrit-MessageType: comment
      Gerrit-Project: chromium/src
      Gerrit-Branch: main
      Gerrit-Change-Id: I8aae3dd40ccdd12623855b4f546415ed1951df48
      Gerrit-Change-Number: 8260949
      Gerrit-PatchSet: 4
      Gerrit-Owner: Andrea Orru <andre...@chromium.org>
      Gerrit-Reviewer: Andrea Orru <andre...@chromium.org>
      Gerrit-Reviewer: Devlin Cronin <rdevlin...@chromium.org>
      Gerrit-Attention: Devlin Cronin <rdevlin...@chromium.org>
      Gerrit-Comment-Date: Sat, 15 Aug 2026 00:35:06 +0000
      satisfied_requirement
      unsatisfied_requirement
      open
      diffy
      Reply all
      Reply to author
      Forward
      0 new messages