Is Udemy Dual Subtitles - Subtitle Translator safe?
Udemy Dual Subtitles sends tab URL, tab title, and a persistent user UUID to its own backend and to Amplitude on every subtitle translation request.
Each time the extension translates subtitles, it POSTs the current tab URL, tab title, source and target languages, and a persistent device UUID to api.udemydualsubtitles.com. Separately, the background service worker forwards tab URL and tab title as properties of every runtime message event to Amplitude's analytics API, including changes to extension storage. The UUID is generated once and stored in chrome.storage.local, allowing the developer to correlate requests across sessions.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Subtitle translation sends Udemy tab details to backend
The source code shows a translation request sends the Udemy tab URL, tab title, page origin, subtitle text, and language choices to api.udemydualsubtitles.com, plus an install UUID from local storage (ga.cid), generated on first use.
You request translated subtitles while viewing a Udemy course page.
The content script packages the selected subtitle text with source and target languages.
The extension posts that subtitle request with the Udemy tab URL, tab title, page origin, and stored installation UUID.
The request goes to api.udemydualsubtitles.com over HTTPS.
| Field | Value | Why it matters | |
|---|---|---|---|
Installation identifier | 4f8a2b7c-91d3-4c6e-a51d-6b0f9a8e1234 | Lets separate translation requests be tied back to the same extension installation. | |
Udemy page URL | https://www.udemy.com/course/python-for-data-science/learn/lecture/12345678 | Shows the exact course or lecture page where you requested translation. | |
Page origin | https://www.udemy.com | Shows the site context for the translation request. | |
Page title | Python for Data Science - Lecture 12: Pandas Basics | Reveals the course or lesson title visible in the browser tab. | |
Subtitle text | In this lecture we will load the CSV file and inspect the first rows. | Contains the caption text selected for translation. | |
Language choices | from=en, to=es | Shows the source and target languages selected for the translation. |
| Content-Type | application/json |
The translation path carries tab details into the backend request
return (
C.debug("sendMessage, cmd: translate, from:", t, "to:", n),
(e.next = 3),
chrome.runtime
.sendMessage({
cmd: "translate",
payload: { from: t, to: n, q: B(r.slice(o, o + u)) },
})
.then(function (e) {
C.debug("sendMessage[translate] rsp:", JSON.stringify(e));
if (!e.errcode) {
_(!1);
_(function (t) {
C.debug("getCues2Processing state:", t);
v(W(r, g, e.rslt));
return t;
});
}
})
);U = (function () {
var e = u(
a().mark(function e() {
var t, n, r;
return a().wrap(function (e) {
for (;;)
switch ((e.prev = e.next)) {
case 0:
return (
(t = f.storageKeys.gaClientId),
(n = null),
(e.next = 1),
chrome.storage.local.get([t])
);
case 1:
return (
(r = e.sent),
m.debug("storage get:", r),
r[t]
? (m.debug("got id from storage: ", r[t]), (n = r[t]))
: ((n = k()),
m.debug("generate id from uuidv4: ", n),
chrome.storage.local.set(p({}, t, n))),
e.abrupt("return", n)
);
case 2:
case "end":
return e.stop();
}
}, e);
}),
);
return function () {
return e.apply(this, arguments);
};
})();(P = e.sent),
(N = n.origin),
(C = null == n || null === (u = n.tab) || void 0 === u ? void 0 : u.url),
(j = null == n || null === (s = n.tab) || void 0 === s ? void 0 : s.title),
(L = t.payload.from),
(U = t.payload.to),
(D = t.payload.q),
(M = O.stringify.call(O, {
ext: "UdemyDualSubtitles",
cid: P,
origin: N,
tab_url: C,
tab_title: j,
from: L,
to: U,
q: D,
})),
(e.next = 4),
x(k, { method: "POST", mode: "cors", headers: T, body: M })
.then(function (e) {
return e.json();
})
.then(function (e) {
return e.trans_result;
})
.catch(function (e) {
return m.error(e), null;
});- api.udemydualsubtitles.com
Extension backend that receives subtitle translation POSTs with cid, origin, tab_url, tab_title, language choices, and q.
Runtime messages attach Udemy tab details to Amplitude
The source code logs each background runtime message as an Amplitude event, Bg Runtime Message, with properties including the request object plus sender.url/tab.title/tab.url, sent to api2.amplitude.com/2/httpapi.
You use an extension feature that sends a message to the background service worker.
Translation, license, and usage commands are routed through the runtime-message handler.
The service worker records the message as an Amplitude analytics event with the current tab URL and title.
The same service worker also records extension storage changes with old and new values.
| Field | Value | Why it matters | |
|---|---|---|---|
Current tab URL | https://www.udemy.com/course/python-for-data-science/learn/lecture/12345678 | Shows which Udemy page was open when the extension processed the message. | |
Current tab title | Python for Data Science - Lecture 12: Pandas Basics | Reveals the course or lesson title visible in the browser tab. | |
Sender URL | https://www.udemy.com/course/python-for-data-science/learn/ | Adds the page context that sent the message to the background service worker. | |
Extension request | cmd=translate, from=en, to=es | Includes the command and payload associated with the feature you used. | |
Changed storage values | key=lastSubtitleLang, oldValue=en, newValue=es | Records extension settings or state before and after a storage update. |
Runtime messages and storage changes flow into Amplitude events
var ce = "".concat("AMP", "_unsent"),
le = "https://api2.amplitude.com/2/httpapi",
fe = "dclid",
de = "fbclid",
pe = "gbraid",
he = "gclid",
ve = "ko_click_id",
ge = "li_fat_id";kr = (function () {
var e = u(
a().mark(function e(t, n) {
var r;
return a().wrap(
function (e) {
for (;;)
switch ((e.prev = e.next)) {
case 0:
return ((e.prev = 0), (e.next = 1), Sr(t, n));
case 1:
e.next = 3;
break;
case 2:
((e.prev = 2), (r = e.catch(0)), m.error("[amp] catch err:", r.message));
case 3:
case "end":
return e.stop();
}
},
e,
null,
[[0, 2]],
);
}),
);
return function (t, n) {
return e.apply(this, arguments);
};
})();kr("Bg Runtime Message", {
extname: f.ext.name,
status: "begin",
cmd: e.cmd,
req: e,
sender: {
url: null == t ? void 0 : t.url,
tab: {
title: null == t || null === (r = t.tab) || void 0 === r ? void 0 : r.title,
url: null == t || null === (i = t.tab) || void 0 === i ? void 0 : i.url,
},
},
});
var o = null;
return "translate" === e.cmd
? (Pr(e, t, n), !0)
: "license" === e.cmd
? (Rr(e, t, n), !0)
: "usage" === e.cmd
? (Cr(e, t, n), !0)
: (m.error("wrong req.cmd"),
n((o = { errcode: -1, errmsg: "wrong req.cmd" })),
void m.debug("rsp:", o));kr("Bg Storage Change", {
extname: f.ext.name,
namespace: t,
key: u,
oldValue: a,
newValue: c,
});o.navigator.sendBeacon(e, JSON.stringify(t))
? n.buildResponse({
code: 200,
events_ingested: t.events.length,
payload_size_bytes: u.length,
server_upload_time: Date.now(),
})
: n.buildResponse({ code: 500 });- api2.amplitude.com
Amplitude HTTP API endpoint that receives extension analytics events and their event properties.