Is Captions for Live Presentations safe?

Medium risk

Captions for Live Presentations sends user IP address, geolocation, and speech transcripts to third-party servers.

On startup, the extension fetches the user's IP address and geolocation from ipapi.co and combines them with device fingerprint data before sending to Google Analytics via the GA4 Measurement Protocol. When the translation feature is enabled, final speech recognition results are transmitted to a developer-operated Google Cloud Run backend over a Socket.IO connection authenticated with a Firebase ID token.

AI Startup Devs LLCv2.4.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

IP location added to analytics startup event

When the extension window opens, its bundle initializes Google Analytics with IP/location data from ipapi.co in analytics properties, plus a client ID and browser/platform/language fields, linking location and device to a persistent ID.

01EvidenceCAUSE EFFECT
What actually happens
You did this

You open the extension window.

The extension did this

The extension initializes analytics, requests your IP-based location, adds device context, and posts the combined user properties to Google Analytics.

02EvidenceFIELD TABLE
Fields assembled into analytics user properties
FieldValueWhy it matters
Persistent analytics ID
1f7b8a9e-4f1a-4e6a-8b33-9b4e2f4c8c2aLets future analytics events from the extension be tied back to the same browser profile.
Your IP address
203.0.113.42 (illustrative)Identifies the network address used for the extension session and supports approximate location lookup.
Approximate location
United States / California / San Francisco / America/Los_Angeles (illustrative)Adds city, region, country, and timezone context to the analytics profile.
Browser and device context
Google Chrome 126, Chrome OS, en-US, 1440x900, mobile=falseAdds details about the browser, platform, language, screen size, and whether the browser appears mobile.
Startup event name
app_initializedMarks the analytics upload as part of the extension startup flow.
03EvidenceNETWORK CAPTURE
Captured request
GEThttps://ipapi.co/json/
04EvidenceNETWORK CAPTURE
Captured request
POSThttps://www.google-analytics.com/mp/collect?measurement_id=G-YLS3JCT978&api_secret=<redacted>
Headers
Content-Typeapplication/json
05EvidenceCODE COMPARE
The code that does this

Analytics initialization path that combines location and device fields

What it actually does
Readable analytics class, ipapi lookup, device fields, and GA postdeobfuscated/bundle.js
        var Gd = function() {
          function e(t) {
            var n = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : {};
            Rd(this, e);
            var r = e,
              s = r.DEFAULT_ENGAGEMENT_TIME_IN_MSEC;
            "start_button_click" === t ? r.engagement_start_time = Date.now() : r.engagement_start_time && (s = Date.now() - r.engagement_start_time, "stop_button_click" === t && (r.engagement_start_time = 0)), this.name = t, this.params = Sd(Sd({}, n), {}, {
              session_id: Date.now().toString(),
              timestamp: (new Date).toISOString(),
              engagement_time_msec: s,
              debug_mode: !0
            }), e.eventData.events.push(this)
          }
          return Nd(e, [{
            key: "postToServer",
            value: (n = Od(Cd().mark((function t() {
              var n, r;
              return Cd().wrap((function(t) {
                for (;;) switch (t.prev = t.next) {
                  case 0:
                    return n = e, t.prev = 1, t.next = 4, fetch("".concat(n.GA_ENDPOINT, "?measurement_id=").concat(n.MEASUREMENT_ID, "&api_secret=").concat(n.API_SECRET), {
                      method: "POST",
                      headers: {
                        "Content-Type": "application/json"
                      },
                      body: JSON.stringify(n.eventData)
                    });
                  case 4:
                    if ((r = t.sent).ok) {
                      t.next = 7;
                      break
                    }
                    throw new Error("GA request failed with status ".concat(r.status));
                  case 7:
                    n.eventData.events = [], t.next = 15;
                    break;
                  case 11:
                    return t.prev = 11, t.t0 = t.catch(1), console.error("Failed to send event to GA:", t.t0), t.abrupt("return", !1);
                  case 15:
                  case 16:
                  case "end":
                    return t.stop()
                }
              }), t, null, [
                [1, 11]
              ])
            }))), function() {
              return n.apply(this, arguments)
            })
          }], [{
            key: "init",
            value: (t = Od(Cd().mark((function t() {
              var n = this;
              return Cd().wrap((function(t) {
                for (;;) switch (t.prev = t.next) {
                  case 0:
                    if (!Id(e, this, np)._) {
                      t.next = 2;
                      break
                    }
                    return t.abrupt("return", Id(e, this, np)._);
                  case 2:
                    np._ = Id(e, this, Promise.all([Id(e, this, Wd).call(this), Id(e, this, Qd).call(this), Id(e, this, Yd).call(this)]).then((function() {
                      return Id(e, n, ep).call(n), new e("app_initialized").postToServer(), !0
                    })).catch((function(t) {
                      throw console.error("Failed to initialize GoogleAnalyticsEvent:", t), np._ = Id(e, n, null), t
                    })));
                  case 4:
                  case "end":
                    return t.stop()
                }
              }), t, this)
            }))), function() {
              return t.apply(this, arguments)
            })
          }]);
          var t, n
        }();

        function Wd() {
          return Jd.apply(this, arguments)
        }

        function Jd() {
          return (Jd = Od(Cd().mark((function e() {
            var t;
            return Cd().wrap((function(e) {
              for (;;) switch (e.prev = e.next) {
                case 0:
                  return e.next = 2, chrome.storage.local.get("clientId");
                case 2:
                  if (t = (t = e.sent).clientId) {
                    e.next = 8;
                    break
                  }
                  return t = self.crypto.randomUUID(), e.next = 8, chrome.storage.local.set({
                    clientId: t
                  });
                case 8:
                  this.eventData.client_id = t;
                case 10:
                case "end":
                  return e.stop()
              }
            }), e, this)
          })))).apply(this, arguments)
        }

        function Qd() {
          return Xd.apply(this, arguments)
        }

        function Xd() {
          return (Xd = Od(Cd().mark((function e() {
            var t, n;
            return Cd().wrap((function(e) {
              for (;;) switch (e.prev = e.next) {
                case 0:
                  return e.prev = 0, e.next = 3, fetch("https://ipapi.co/json/");
                case 3:
                  return t = e.sent, e.next = 6, t.json();
                case 6:
                  n = e.sent, this.eventData.user_properties = Sd(Sd({}, this.eventData.user_properties), {}, {
                    country: {
                      value: n.country_name
                    },
                    region: {
                      value: n.region
                    },
                    city: {
                      value: n.city
                    },
                    timezone: {
                      value: n.timezone
                    },
                    ip: {
                      value: n.ip
                    }
                  }), e.next = 13;
                  break;
                case 10:
                  e.prev = 10, e.t0 = e.catch(0), console.error("failed to retrieve user data");
                case 13:
                case 14:
                case "end":
                  return e.stop()
              }
            }), e, this, [
              [0, 10]
            ])
          })))).apply(this, arguments)
        }

        function Yd() {
          return Zd.apply(this, arguments)
        }

        function Zd() {
          return Zd = Od(Cd().mark((function e() {
            var t, n, r;
            return Cd().wrap((function(e) {
              for (;;) switch (e.prev = e.next) {
                case 0:
                  return n = function() {
                    return n = Od(Cd().mark((function e() {
                      return Cd().wrap((function(e) {
                        for (;;) switch (e.prev = e.next) {
                          case 0:
                            return e.abrupt("return", navigator.userAgentData.getHighEntropyValues(["brands"]).then((function(e) {
                              var t = e.brands.find((function(e) {
                                return !e.brand.includes("Chromium") && !e.brand.includes("Not A")
                              }));
                              return t ? "".concat(t.brand, " ").concat(t.version) : "Unknown Browser"
                            })));
                          case 1:
                          case "end":
                            return e.stop()
                        }
                      }), e)
                    }))), n.apply(this, arguments)
                  }, t = function() {
                    return n.apply(this, arguments)
                  }, e.next = 5, t();
                case 5:
                  r = e.sent, this.eventData.user_properties = Sd(Sd({}, this.eventData.user_properties), {}, {
                    browser: {
                      value: r
                    },
                    platform: {
                      value: navigator.userAgentData.platform
                    },
                    language: {
                      value: navigator.language
                    },
                    screenWidth: {
                      value: window.screen.width
                    },
                    screenHeight: {
                      value: window.screen.height
                    },
                    isMobile: {
                      value: /Mobi|Android/i.test(navigator.userAgent)
                    }
                  });
                case 7:
                case "end":
                  return e.stop()
              }
            }), e, this)
          }))), Zd.apply(this, arguments)
        }

        function ep() {
          return tp.apply(this, arguments)
        }

        function tp() {
          return tp = Od(Cd().mark((function e() {
            var t;
            return Cd().wrap((function(e) {
              for (;;) switch (e.prev = e.next) {
                case 0:
                  t = function() {
                    var e = Od(Cd().mark((function e(t, n) {
                      var r, s, i, o, a;
                      return Cd().wrap((function(e) {
                        for (;;) switch (e.prev = e.next) {
                          case 0:
                            if (!(Ed.errorHandlingInProgress || null !== (r = n.reason) && void 0 !== r && null !== (r = r.stack) && void 0 !== r && r.includes("GoogleAnalyticsEvent"))) {
                              e.next = 2;
                              break
                            }
                            return e.abrupt("return");
                          case 2:
                            return Ed.errorHandlingInProgress = !0, a = {
                              type: t,
                              message: (null === (s = n.reason) || void 0 === s ? void 0 : s.message) || n.message || "Unknown error",
                              stack: (null === (i = n.reason) || void 0 === i ? void 0 : i.stack) || (null === (o = n.error) || void 0 === o ? void 0 : o.stack) || "No stack available",
                              source: n.filename || "N/A",
                              line: n.lineno || 0,
                              column: n.colno || 0
                            }, e.next = 6, new Ed("extension_error", a).postToServer();
                          case 6:
                            Ed.errorHandlingInProgress = !1;
                          case 7:
                          case "end":
                            return e.stop()
                        }
                      }), e)
                    })));
                    return function(t, n) {
                      return e.apply(this, arguments)
                    }
                  }(), addEventListener("unhandledrejection", (function(e) {
                    return t("promise_rejection", e)
                  })), addEventListener("error", (function(e) {
                    return t("runtime_error", e)
                  }));
                case 3:
                case "end":
                  return e.stop()
              }
            }), e)
          }))), tp.apply(this, arguments)
        }
        Ed = Gd, Ad(Gd, "GA_ENDPOINT", "https://www.google-analytics.com/mp/collect"), Ad(Gd, "GA_DEBUG_ENDPOINT", "https://www.google-analytics.com/debug/mp/collect"), Ad(Gd, "DEFAULT_ENGAGEMENT_TIME_IN_MSEC", 0), Ad(Gd, "engagement_start_time", 0), Ad(Gd, "eventData", {
          client_id: "",
          events: [],
          user_properties: {}
        }), Ad(Gd, "errorHandlingInProgress", !1);
        var np = {
          _: null
        };
        Ad(Gd, "MEASUREMENT_ID", "G-YLS3JCT978"), Ad(Gd, "API_SECRET", "<redacted>");
06EvidenceTHIRD PARTY LIST
External services involved in the startup analytics flow
  • ipapi.co

    Receives the location lookup request and returns IP-derived country, region, city, timezone, and IP fields.

  • www.google-analytics.com

    Receives the app_initialized analytics event with client ID, location fields, and browser/device properties.

Data recipients

ipapi.cowww.google-analytics.comhello-there-cloudrun-987736763956.us-central1.run.apptranslate.googleapis.com
Updated 17 September 2026ckpgjpenbhnkhcalechemlifbcinneen