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

Read to Me sends page URLs to Amplitude on widget use

MediumunwantedCWE-359Dynamic sandbox

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.

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.

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-Type
application/json

Fields observed in the Amplitude event

  • Current page URL
    https://en.wikipedia.org/wiki/Canary

    Shows the exact page where you used the widget, which can reveal the site and page topic.

  • Extension user ID
    94fe7879-beff-4e0e-8ec8-3313f6bc8058

    Lets repeated widget events from the same browser be linked together over time.

  • Browser language
    en-US

    Adds regional context to the event and helps distinguish users or environments.

  • Extension version
    3.0.1

    Shows which installed version generated the event.

  • Widget event type
    widget_opened

    Shows 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.

The code that does this

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

Readable version

Deobfuscated analytics sender

background.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 helper

background.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  }

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.

Where it sends data

Destinations our analysis observed Read to Me contacting. Sending data somewhere is not a finding on its own - an extension that syncs your settings has to talk to its own server - but it is where your data can go, and who else it goes to.

  • api2.amplitude.comwidely used

    Read to Me sends data to api2.amplitude.com. A widely used service: 42 other extensions we have analyzed send data here.

  • o4508767065079808.ingest.us.sentry.io

    Read to Me sends data to o4508767065079808.ingest.us.sentry.io. No other extension we have analyzed sends data here.

Updated 21 September 2026anpkfnccdmljhdegcaonjffhjhhcalaj