Is Immersive Reader - Improve Reading Accessibility safe?

High risk

Immersive Reader - Improve Reading Accessibility sends login details and selected page text to Amplitude analytics.

When users submit the extension's account login flow, the extension forwards the login request object, including email and verification code fields, through Amplitude telemetry. When users choose its read-selection context menu item, it also sends the selected page text and page URL in an Amplitude event.

75Risk
Who publishes it

Mark - 5 other listings from the same operator, 2 of them carrying a finding

What this publisher told the store about itself, and the other listings that told it the same thing.

Publisher
Mark

Shared hosts - 1 hostname

Hostnames hardcoded in this extension that few other listings call. That can mean one operator behind both, and it can equally mean a small shared vendor, so it is context rather than a conclusion. Hosts that many listings call are left out: they are services, not connections.

ircdname.azureedge.net
Also called by 3 other listings, including Kami, Helperbird

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

Publishers can request a review.

Findings

Login codes are passed into Amplitude analytics

Submitting the account login form sends your email and code to the background worker, which logs the full runtime request in an Amplitude event 'Bg Runtime Msg' before handling it; the client posts to api2.amplitude.com/2/httpapi.

Severity
High unwanted
Type
Unexpected
CWE
CWE-522
Source
Dynamic sandbox
What actually happens
You did this

You submit the extension account login form.

The form requires an email address and the verification code sent for that account.

The extension did this

The background service worker passes the full login request into an Amplitude analytics event.

The request object contains the account command, login subcommand, email address, and verification code.

Fields included in the login request that is passed to analytics
  • Email address
    employee@example.com (illustrative)

    Identifies the account you are trying to sign in to through the extension.

  • Verification code
    482913 (illustrative)

    Represents the one-time code you typed during the account login flow.

  • Login command
    account / login

    Shows that the message belongs to the account login flow, not a general page event.

  • Extension context
    Immersive Translate, version 2.2.0

    Adds extension name and version context around the account request.

Captured request
POSThttps://api2.amplitude.com/2/httpapi

Amplitude HTTP API endpoint configured by the bundled client; no request body was recorded in the available evidence.

The code that does this

The login form payload is logged before the account handler runs

Readable version

Login click handler from home.js

home.js
R = function() {  var e = bt(xt().mark((function e() {    return xt().wrap((function(e) {      for (;;) switch (e.prev = e.next) {        case 0:          gr.debug("in PanelAccount handleClickLogin"), e.prev = 1;        case 2:          if (E(!0), b) {            e.next = 6;            break          }          return alert("Please input login code"), e.abrupt("break", 9);        case 6:          return e.next = 8, chrome.runtime.sendMessage({            cmd: "account",            payload: {              cmd: "login",              payload: {                email: v,                verifycode: b              }            }          }).then((function(e) {            var t;            if (gr.debug("sendMsg[account:login] rsp:", e), 0 !== e.errcode) alert(e.errmsg || "login error");            else if (0 !== (null === (t = e.rslt) || void 0 === t ? void 0 : t.errcode)) {              var n;              alert((null === (n = e.rslt) || void 0 === n ? void 0 : n.errmsg) || "login error")            } else h(0)          }));        case 8:        case 9:          e.next = 15;          break;        case 11:          e.prev = 11, e.t0 = e.catch(1), gr.debug("handleClickLogin catch err:", e.t0.message), alert(e.t0.message);        case 15:          return e.prev = 15, E(!1), e.finish(15);        case 18:        case "end":          return e.stop()      }    }), e, null, [      [1, 11, 15, 18]    ])  })));  return function() {    return e.apply(this, arguments)  }}()

Background runtime-message listener from background.js

background.js
chrome.runtime.onMessage.addListener((function(e, t, n) {  if (b.debug("in handleRuntimeOnMessage, req:", e), b.debug("in handleRuntimeOnMessage, sender:", t), yn("Bg Runtime Msg", {      extname: p.ext.name,      status: "begin",      cmd: e.cmd,      req: e    }), "access" === e.cmd) return wn(e, t, n), !0;  if ("read_selection" === e.cmd) {    var r = function() {      var t = a(l().mark((function t() {        var r, i, o, u;        return l().wrap((function(t) {          for (;;) switch (t.prev = t.next) {            case 0:              return r = null, t.prev = 1, t.next = 4, chrome.tabs.query({                active: !0,                currentWindow: !0              });            case 4:              return i = t.sent, o = s(i, 1), u = o[0], b.debug("current tab:", u), t.next = 10, chrome.scripting.executeScript({                target: {                  tabId: u.id                },                files: ["content.js"]              });            case 10:              r = {                errcode: 0,                errmsg: "processing..."              }, t.next = 17;              break;            case 13:              t.prev = 13, t.t0 = t.catch(1), b.error("catch err:" + t.t0.message), r = {                errcode: -1,                errmsg: "catch err:" + t.t0.message              };            case 17:              b.debug("sendrsp:", JSON.stringify(r)), n(r), yn("Bg Runtime Msg", {                extname: p.ext.name,                status: "end",                cmd: e.cmd,                rsp: r              });            case 20:            case "end":              return t.stop()          }        }), t, null, [          [1, 13]        ])      })));      return function() {        return t.apply(this, arguments)      }    }();    return r(), !0  }  if ("account" === e.cmd) return In(e, t, n), !0;  if ("open_page" === e.cmd) {    chrome.tabs.create({      url: e.payload.url    });    var i = {      errcode: 0,      errmsg: "success"    };    b.debug("sendrsp:", JSON.stringify(i)), n(i), yn("Bg Runtime Msg", {      extname: p.ext.name,      status: "end",      cmd: e.cmd,      rsp: i    })  } else {    var o = {      errcode: -1,      errmsg: "wrong req.msg"    };    b.debug("sendrsp:", JSON.stringify(o)), n(o), yn("Bg Runtime Msg", {      extname: p.ext.name,      status: "end",      cmd: e.cmd,      rsp: o    })  }}))

Amplitude setup from background.js

background.js
t = {  name: "production",  backend: "https://api.immersivereader.org/api",  web: "",  amp_key: "9ab9d6d011bd7a8b870db400e2545d28"}, e.exports = {  env: t}var G = "AMP",  Y = "".concat(G, "_unsent"),  Z = "https://api2.amplitude.com/2/httpapi"yn = Gt.trackb.debug("background.js run, date: ", Date()), a(l().mark((function e() {  return l().wrap((function(e) {    for (;;) switch (e.prev = e.next) {      case 0:        return e.t0 = t, e.t1 = p.amp.key, e.t2 = void 0, e.next = 5, U();      case 5:        e.t3 = e.sent, e.t4 = {          defaultTracking: !1,          deviceId: e.t3        }, e.t0.init.call(e.t0, e.t1, e.t2, e.t4);      case 8:      case "end":        return e.stop()    }  }), e)})))();
External analytics destination named by the code
    • api2.amplitude.com

    Amplitude HTTP analytics endpoint that receives events from the bundled Amplitude client.

    • api.immersivereader.org

    Extension backend used for account API calls; the same login request is separately passed into the analytics event before this account handler runs.

Selected text and page URLs sent to Amplitude from context-menu clicks

When you use the extension's selection context menu, it forwards the click data to Amplitude at api2.amplitude.com/2/httpapi, including selected page text and page URL, and sometimes related link, image, or frame URLs.

Severity
Medium unwanted
Type
Unexpected
CWE
CWE-359
Source
Dynamic sandbox
What actually happens
You did this

User selects text and clicks the extension's context-menu item.

The extension did this

The extension sends the context-menu click data, including selected text and page URL, to Amplitude analytics.

Captured request
POSTapi2.amplitude.com/2/httpapi
Fields in the request
  • Selected page text
    Selected paragraph text (illustrative)

    Text selected on the page is included in the analytics event, which can reveal page content the user highlighted.

  • Page URL
    https://example.com/article (illustrative)

    The source page address is included with the context-menu event, linking the selection to a specific page.

What it can do

Permissions this extension asks for, as declared in version 2.2.0. Asking for a permission is not a finding on its own - it is what the extension can do if it chooses to.

  • Read and change your data on immersivereader.org

    https://*.immersivereader.org/

  • Store data in your browser

    storage

  • Add items to the right-click menu

    contextMenus

  • Act on the current tab, but only after you click the extension

    activeTab

  • Run its own code inside the pages you visit

    scripting

Where it sends data

Destinations our analysis observed Immersive Reader - Improve Reading Accessibility 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

    Immersive Reader - Improve Reading Accessibility sends data to api2.amplitude.com. A widely used service: 41 other extensions we have analysed send data here.

Updated 30 September 2026daafgeilbkllajbkopnbjlmdkgmnpcph