Is GradeTransferer - Transfer, Scale & Sort Grades | Save Time, Reduce Errors safe?

Medium risk

GradeTransferer is medium risk. Logging in, signing up, or another event posts a GA request to google-analytics.com. Built from local storage: extension UID as client ID, stored portal account ID, school email-domain. Its collection credential is redacted here.

Razor Sparrow LLCv3.2.0Chrome Web Store
45Risk
Who publishes it

Razor Sparrow LLC - no other listings under this identity

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

Publisher
Razor Sparrow LLC
Declared legal entity
Razor Sparrow LLC
Registered address
Five Greentree Centre, STE. 104, 525 Route 73 North, Marlton, NJ 08053, US
Registered contact
David Polgar

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

Publishers can request a review.

Findings

Analytics events include account and school-domain fields

Logging in, signing up, or another event posts a GA request to google-analytics.com.

Built from local storage: extension UID as client ID, stored portal account ID, school email-domain.

Its collection credential is redacted here.

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

You log in, sign up, or use a feature that asks the service worker to send an analytics event.

The extension did this

The service worker sends Google Analytics an event that includes stored account and school-domain fields.

Captured request
POSThttps://www.google-analytics.com/mp/collect?measurement_id=G-YNF45EMLTW&api_secret=<redacted>
Fields the service worker attaches to the analytics event
  • Extension user ID
    client_id=7b6d8c52-41f9-4b7f-9a1d-6d7b2c4e8f10 (illustrative)

    Lets the analytics service tie events from the same extension installation or account together over time.

  • Portal account ID
    portal_id=4829137 (illustrative)

    Links the analytics event to the stored account record the extension uses for GradeTransferer service access.

  • School email domain
    email_domain=lincoln.k12.ca.us (illustrative)

    Reveals the school or organization domain associated with the stored account record.

  • Analytics session ID
    session_id=1762839145123 (illustrative)

    Groups multiple extension actions into the same analytics session.

The code that does this

The shipped service worker builds and sends the Measurement Protocol event

Readable version

Readable equivalent of Kt()

service-worker.js
async function sendAnalyticsEvent(eventName, params = {}) {  const accountFields = await (async function () {    let stored = await chrome.storage.local.get(["portalID", "uid", "searchIndex"]);    stored.sessionID = await (async function () {      let { gaSessionData } = await chrome.storage.session.get("gaSessionData");      const now = Date.now();      if (gaSessionData && gaSessionData.timestamp) {        if ((now - gaSessionData.timestamp) / 60000 > 60) {          gaSessionData = null;        } else {          gaSessionData.timestamp = now;          await chrome.storage.session.set({ gaSessionData });        }      }      if (!gaSessionData) {        gaSessionData = { session_id: now.toString(), timestamp: now.toString() };        await chrome.storage.session.set({ gaSessionData });      }      return gaSessionData.session_id;    })();    return stored;  })();  params.engagement_time_msec = 100;  params.session_id = accountFields.sessionID;  params.portal_id = accountFields.portalID;  params.email_domain = accountFields.searchIndex;  await fetch("https://www.google-analytics.com/mp/collect?measurement_id=G-YNF45EMLTW&api_secret=<redacted>", {    method: "POST",    body: JSON.stringify({      client_id: accountFields.uid,      events: [{ name: eventName, params }]    })  });  if (!s) {    u(`Sent ${eventName} GA event for ${accountFields.uid}`, params);  }}

Readable equivalent of the event triggers

service-worker.js
chrome.runtime.onMessageExternal.addListener((message, sender, sendResponse) => {  if (message.type && message.type === "GT_SIGNUP") {    u("Received GT_SIGNUP message direct from authpage");    sendAnalyticsEvent("sign-up", { method: message.method });    sendResponse(undefined);  } else if (message.type && message.type === "GT_AUTHENTICATED") {    u("Received GT_AUTHENTICATED message direct from authpage");    (async () => {      try {        const result = await finishAuthentication(message.auth);        if (result.Ok) {          sendAnalyticsEvent("login", { method: message.auth.method });          sendResponse({ type: "GT_AUTHENTICATED_RESPONSE", source: "sendMessage" });        } else {          sendResponse({ type: "GT_AUTHENTICATED_RESPONSE", error: result.Error, source: "sendMessage" });        }      } catch (error) {        sendResponse({ type: "GT_AUTHENTICATED_RESPONSE", error: error?.message ?? String(error), source: "sendMessage" });      }    })();    return true;  }});chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {  if (message.type && message.type === "GT_AUTHENTICATED") {    u("Received GT_AUTHENTICATED message from content script");    (async () => {      try {        const result = await finishAuthentication(message.auth);        if (result.Ok) {          sendResponse({ type: "GT_AUTHENTICATED_RESPONSE" });        } else {          sendResponse({ type: "GT_AUTHENTICATED_RESPONSE", error: result.Error });        }      } catch (error) {        sendResponse({ type: "GT_AUTHENTICATED_RESPONSE", error: error?.message ?? String(error) });      }    })();    return true;  }  if (message.type === "uploadExtensionStorage") {    (async () => {      await uploadExtensionStorage();      sendResponse();    })();    return true;  }  if (message.type === "sendEventToGA") {    sendAnalyticsEvent(message.name, message.params);    sendResponse();  }});
External destination for the event
    • www.google-analytics.com

    Receives Google Analytics Measurement Protocol events with the extension UID, portal account ID, school email domain, event name, method parameter, session ID, and engagement time.

Updated 30 September 2026boapjdphamdkfjfdibbdpcemcdncfhjf