Is Read to Me safe?

Medium risk

Read to Me captures DOM snapshots of every page you visit and sends them to Sentry, and transmits page URLs to Amplitude on each user interaction.

The extension's content script runs on all websites and uses an embedded rrweb library to record full-page DOM snapshots and mutations, which are compressed and sent to Sentry's session replay endpoint. With maskAllText disabled, visible text on any visited page may be included in these recordings. Additionally, the extension sends the current page URL, language, and a persistent user identifier to Amplitude each time the user opens or interacts with its text-to-speech widget.

ReadtoMev3.0.1Chrome Web Store
45Risk

AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.

Publishers can request a review.

Findings

SeverityMEDIUM
ClassUNWANTED
TypeUnexpected
CWECWE-359
SourceAI SANDBOX

Read to Me sends page URLs to Amplitude on widget use

Read to Me's widget POSTs to Amplitude when opened on visited pages, recording the page URL, a persistent user ID, browser language, extension version, and event type.

The endpoint also receives the request's network IP address.

01EvidenceCAUSE EFFECT
What actually happens
You did this

You open or interact with the Read to Me widget on a visited web page.

Observed events included widget openings after using the toolbar action on several websites.

The extension did this

The extension sends an analytics event to Amplitude with the page URL and a persistent extension user ID.

The same request also includes browser language, extension version, event type, and platform.

02EvidenceNETWORK CAPTURE
Captured request
POSThttps://api2.amplitude.com/2/httpapi
Observed during dynamic analysis: five content-script POST requests were sent; four were widget_opened events containing the visited page URL.
Headers
Content-Typeapplication/json
03EvidenceFIELD TABLE
Fields observed in the Amplitude event
FieldValueWhy it matters
Current page URL
https://en.wikipedia.org/wiki/CanaryShows the exact page where you used the widget, which can reveal the site and page topic.
Extension user ID
94fe7879-beff-4e0e-8ec8-3313f6bc8058Lets repeated widget events from the same browser be linked together over time.
Browser language
en-USAdds regional context to the event and helps distinguish users or environments.
Extension version
3.0.1Shows which installed version generated the event.
Widget event type
widget_openedShows what you did with the widget, such as opening it or pressing play.
Network IP address
203.0.113.24 (illustrative)The remote analytics endpoint receives the network address used to make the request.
04EvidenceCODE COMPARE
The code that does this

The sender builds an Amplitude event with window.location.href

What it actually does
Deobfuscated analytics senderbackground.js
var E = function() {
      var t, r = (t = v().mark((function t(r, e) {
        var n, o, i;
        return v().wrap((function(t) {
          for (;;) switch (t.prev = t.next) {
            case 0:
              return t.prev = 0, t.next = 3, p();
            case 3:
              return o = t.sent, i = {
                api_key: "21a85efdb6d6e94bf888af0f5325c2b2",
                events: [{
                  user_id: o,
                  event_type: r,
                  event_properties: g({
                    extension_version: chrome.runtime.getManifest().version,
                    time: (new Date).toISOString(),
                    url: r !== x.EXTENSION_INSTALLED ? window.location.href : null,
                    platform: "Chrome Extension",
                    country: navigator.language || (null === (n = navigator.languages) || void 0 === n ? void 0 : n[0])
                  }, e)
                }]
              }, t.next = 7, fetch("https://api2.amplitude.com/2/httpapi", {
                method: "POST",
                headers: {
                  "Content-Type": "application/json"
                },
                body: JSON.stringify(i)
              });
            case 7:
              t.sent.ok, t.next = 14;
              break;
            case 11:
              t.prev = 11, t.t0 = t.catch(0);
            case 14:
            case "end":
              return t.stop()
          }
        }), t, null, [
          [0, 11]
        ])
      })), function() {
        var r = this,
          e = arguments;
        return new Promise((function(n, o) {
          var i = t.apply(r, e);

          function a(t) {
            w(i, n, o, a, c, "next", t)
          }

          function c(t) {
            w(i, n, o, a, c, "throw", t)
          }
          a(void 0)
        }))
      });
      return function(t, e) {
        return r.apply(this, arguments)
      }
    }(),
    x = {
      WIDGET_OPENED: "widget_opened",
      WIDGET_CLOSED: "widget_closed",
      EXTENSION_INSTALLED: "extension_installed",
      TTS_PLAY_CLICKED: "tts_play_clicked",
      WIDGET_OPEN_ERROR: "widget_open_error",
      VOICES_LOAD_ERROR: "voices_load_error",
      PDF_READER_OPENED: "pdf_reader_opened"
    };
Deobfuscated persistent user ID helperbackground.js
function p() {
    return y.apply(this, arguments)
  }

  function y() {
    return (t = c().mark((function t() {
      var r, e;
      return c().wrap((function(t) {
        for (;;) switch (t.prev = t.next) {
          case 0:
            return t.prev = 0, t.next = 3, s(["clientId"]);
          case 3:
            if (!(r = t.sent).clientId) {
              t.next = 6;
              break
            }
            return t.abrupt("return", r.clientId);
          case 6:
            return e = o(), t.next = 9, f({
              clientId: e
            });
          case 9:
            return t.abrupt("return", e);
          case 13:
            return t.prev = 13, t.t0 = t.catch(0), t.abrupt("return", o());
          case 17:
          case "end":
            return t.stop()
        }
      }), t, null, [
        [0, 13]
      ])
    })), y = function() {
      var r = this,
        e = arguments;
      return new Promise((function(n, o) {
        var i = t.apply(r, e);

        function a(t) {
          u(i, n, o, a, c, "next", t)
        }

        function c(t) {
          u(i, n, o, a, c, "throw", t)
        }
        a(void 0)
      }))
    }).apply(this, arguments);
    var t
  }
05EvidenceTHIRD PARTY LIST
External destination receiving the analytics event
  • api2.amplitude.com

    Amplitude HTTP API endpoint that receives widget analytics events containing the current page URL, persistent extension user ID, language, event type, and extension version.

Data recipients

o4508767065079808.ingest.us.sentry.ioapi2.amplitude.com
Updated 17 September 2026anpkfnccdmljhdegcaonjffhjhhcalaj