Is GradeTransferer - Transfer, Scale & Sort Grades | Save Time, Reduce Errors safe?
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.
Who publishes itRazor Sparrow LLC - no other listings under this identity
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.
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
You log in, sign up, or use a feature that asks the service worker to send an analytics event.
The service worker sends Google Analytics an event that includes stored account and school-domain fields.
- Extension user IDclient_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 IDportal_id=4829137 (illustrative)
Links the analytics event to the stored account record the extension uses for GradeTransferer service access.
- School email domainemail_domain=lincoln.k12.ca.us (illustrative)
Reveals the school or organization domain associated with the stored account record.
- Analytics session IDsession_id=1762839145123 (illustrative)
Groups multiple extension actions into the same analytics session.
The shipped service worker builds and sends the Measurement Protocol event
Readable equivalent of Kt()
service-worker.jsasync 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.jschrome.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(); }});- 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.