Is Manga Translator safe?
Manga Translator is medium risk. Clicking Manga Translator's screenshot button makes the content script capture the area via the service worker and send the PNG data URL to api.ismanga.com/graphql, with the page URL, flagged as a screenshot translation.…
Who publishes itismanga - no other listings under this identity
ismanga - 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
Screenshot translation uploads the active-tab image
Clicking Manga Translator's screenshot button makes the content script capture the area via the service worker and send the PNG data URL to api.ismanga.com/graphql, with the page URL, flagged as a screenshot translation.
What actually happens
You click the screenshot translation button in the extension overlay.
The button is rendered as clickToTranslateButton in the content script.
The extension captures the visible tab image and submits it with the page URL.
The request goes to the Manga Translator GraphQL API for screenshot-based translation.
Fields constructed for the screenshot translation request
- Visible page imagedata:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+/p9sAAAAASUVORK5CYII= (illustrative)
Lets the translation service see the portion of the page captured for screenshot translation.
- Current page URLhttps://mangadex.org/chapter/7f3b0c2e-1a3d-4c5b-98f0-23a2b41c9d75/12 (illustrative)
Ties the screenshot to the page you were viewing when you asked for translation.
- Screenshot markerisScreenshot: true
Tells the service that this upload came from screenshot translation rather than URL translation.
- Device identifier1d7d2df7-65d7-41e2-8f41-8a6e6b1c2d49 (illustrative)
Can link multiple translation requests from the same browser profile over time.
Captured request
Dynamic analysis did not reach the screenshot button, so no captured request body or response is available.
- Accept
- application/json
- Content-Type
- application/json
The code that does this
The overlay click leads to a service-worker screenshot and GraphQL upload
Click handler captures a PNG data URL and hands it to the translation callback
dist/content.bundle.jsonClick: function() { var t = (0, e.A)(s().mark((function e(t) { var n, a, r, o; return s().wrap((function(e) { for (;;) switch (e.prev = e.next) { case 0: if (t.preventDefault(), t.stopPropagation(), w) { e.next = 1; break } return window.open("".concat(k.ISMANGA_SUBSCRIPTION_PRICING_ENDPOINT, "/").concat((null == p ? void 0 : p.uid) || ""), "_blank"), e.abrupt("return"); case 1: if (b.current && d && f) { e.next = 2; break } return e.abrupt("return"); case 2: return b.current.style.visibility = "hidden", e.next = 3, (0, g.getTargetDataUrl)({ targetElement: d, screenshotViewport: f }); case 3: return n = e.sent, a = (0, y.getDataUrlId)(n), r = Boolean, e.next = 4, c({ imgDataUrl: n, imgDataUrlId: a }); case 4: o = e.sent, r(o) && (b.current.style.visibility = "visible"); case 5: case "end": return e.stop() } }), e) }))); return function(e) { return t.apply(this, arguments) }}()Content script asks the service worker to take a screenshot
dist/content.bundle.jsvar ml = function() { var e = t(r().mark((function e() { var t, n; return r().wrap((function(e) { for (;;) switch (e.prev = e.next) { case 0: return e.next = 2, chrome.runtime.sendMessage({ code: "take_screenshot" }); case 2: return t = e.sent, n = t.payload, e.abrupt("return", n); case 5: case "end": return e.stop() } }), e) }))); return function() { return e.apply(this, arguments) }}()Service worker captures the active tab image
dist/background.bundle.jschrome.runtime.onMessage.addListener((function(e, t, r) { return "take_screenshot" === e.code && (n(s().mark((function e() { var t; return s().wrap((function(e) { for (;;) switch (e.prev = e.next) { case 0: return e.next = 2, chrome.tabs.captureVisibleTab(); case 2: t = e.sent, r({ code: xr, payload: t }); case 4: case "end": return e.stop() } }), e) })))(), !0)}))Screenshot data URL is placed into createMangaByDataUrl
dist/content.bundle.jsfunction(e) { var t = e.imgDataUrl, n = e.imgDataUrlId, a = e.originUrl, r = e.toLang, s = void 0 === r ? o.LANG_CODE.EN_US : r, l = e.fromLang, u = e.isScreenshot, c = void 0 !== u && u, d = e.shouldSkipCache, p = void 0 !== d && d, f = e.shouldForceRefresh, m = void 0 !== f && f, g = e.idToken, h = e.visitId, b = e.deviceId, y = JSON.stringify({ query: 'mutation {\n createMangaByDataUrl(\n dataUrl: "'.concat(t, '",\n dataUrlId: "').concat(n, '",\n originUrl: "').concat(a, '",\n langCode: "').concat(s, '",\n selectedLangCode: "').concat(l, '",\n isScreenshot: ').concat(c, ",\n shouldSkipCache: ").concat(p, ",\n shouldForceRefresh: ").concat(m, "\n ) { \n code\n payload {\n x, \n y, \n width \n height \n fromLang {\n code\n script\n }\n toLang {\n code\n script\n }\n imgWidth\n imgHeight\n inpaintingUrl\n colors {\n text\n background\n }\n }\n taskPayload {\n taskId\n url\n isPseudoUrl\n isHighQuality\n langCode\n shouldCache\n }\n }\n }\n ") }); return (0, i.post)({ idToken: g, visitId: h, deviceId: b, init: { body: y } }).then((function(e) { return e.json() }))}External service receiving the screenshot translation request
- api.ismanga.com
Receives the GraphQL createMangaByDataUrl request containing the screenshot PNG data URL, current page URL, language settings, and request metadata.
Tracked UI Clicks Post Event Metadata
Manga Translator attaches click tracking to UI elements and posts a logEvent mutation to api.ismanga.com/graphql.
The builder can serialize URL parts when supplied; the inspected handler omits URL.
Dynamic analysis saw no logEvent traffic.
What actually happens
You click a Manga Translator UI control on a page.
The click must occur on an element, or a parent element, with Manga Translator component metadata.
The extension prepares a GraphQL event record and posts it to Manga Translator's API.
The record includes the click event name, component, container, platform, and visit identifier; URL slots are populated only if the caller supplies a URL.
Fields assembled for the logEvent mutation
- Event nameClick
Describes the kind of interaction you made with the extension interface.
- Clicked componenttranslateNowButton
Identifies which Manga Translator control you clicked.
- ContainercontrolPanel
Adds the surrounding Manga Translator UI area for the clicked control when that metadata exists.
- Visit identifier8f2f3d0a-5f7a-4e4f-9a21-0e9c9817d118
Lets the service connect the click event to the same extension visit session.
- Optional URL fieldshostname=mangadex.org; pathname=/chapter/6f4c8f3b; query=?tab=comments
Can describe the page path, host, and query string if a URL is supplied to the tracking helper.
Captured request
Dynamic analysis observed GraphQL traffic to this endpoint, but did not capture a logEvent mutation after popup clicks; the page overlay controls were not successfully clicked.
- Accept
- application/json
- Content-Type
- application/json
The code that does this
Click metadata extraction and logEvent POST construction
Readable page-level click listener
deobfuscated/dist/content.bundle.jswindow.addEventListener("click", (function(e) { var t; t = { event: e, visitId: n, idToken: l }, (0, Da.handleClickTracking)(function(e) { for (var t = 1; t < arguments.length; t++) { var n = null != arguments[t] ? arguments[t] : {}; t % 2 ? lr(Object(n), !0).forEach((function(t) { Ca(e, t, n[t]) })) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(n)) : lr(Object(n)).forEach((function(t) { Object.defineProperty(e, t, Object.getOwnPropertyDescriptor(n, t)) })) } return e }({ platform: "extension" }, t))}), { capture: !0, passive: !0})Readable component lookup and click tracking helper
deobfuscated/dist/content.bundle.jsi = function(e, t) { for (var n = e instanceof HTMLElement ? e : null; n;) { var a, r = null === (a = n.dataset) || void 0 === a ? void 0 : a[t]; if (r) return r; n = n.parentElement } return null};s = function(n) { var a = n.platform, r = n.event, o = n.idToken, s = n.visitId, l = n.url, u = i(r.target, "ismangaComponent"); if (u) { var c = i(r.target, "ismangaContainer"), d = function(e) { if (e) { if ("string" != typeof e) return e; try { var t = new URL(e); return { pathname: t.pathname, hostname: t.hostname, query: t.search } } catch (e) { return } } }(l); (0, e.default)({ eventName: t.TRACKING_EVENTS.CLICK, idToken: o, visitId: s, component: u, container: null != c ? c : "", platform: null != a ? a : "", url: d }) }}Readable logEvent mutation helper
deobfuscated/dist/content.bundle.jsfunction(t) { var n, a, r, s, l, u, c, d, p, f, m, g; n = t.eventName; a = t.url; r = t.component; s = t.container; l = t.idToken; u = t.visitId; c = t.platform; d = t.extra; p = void 0 === d ? "" : d; f = t.sessionId; m = t.sessionDurationSeconds; g = JSON.stringify({ query: 'mutation {\n logEvent(\n platform: "'.concat(c, '", \n url: {\n pathname: "').concat(null == a ? void 0 : a.pathname, '",\n hostname: "').concat(null == a ? void 0 : a.hostname, '",\n query: "').concat(null == a ? void 0 : a.query, '"\n },\n eventName: "').concat(n, '",\n component: "').concat(r, '",\n container: "').concat(s, '",\n extra: "').concat(p, '"\n ').concat(f ? ", sessionId: ".concat(f) : "", "\n ".concat(m ? ", sessionDurationSeconds: ".concat(m) : "", "\n ) {\n success\n message\n }\n }") }); return (0, i.post)({ visitId: u, idToken: l, init: { body: g } })}Remote destination used by this code path
- api.ismanga.com
Receives Manga Translator GraphQL requests, including logEvent when tracked UI controls call the event helper.
Persistent cross-session tracking UUID sent on every API call
Dynamic analysis captured 25 POSTs to api.ismanga.com/graphql in two sessions, each with the same ismanga-visit-id header, a UUID stored in chrome.storage.sync.
Sign-in triggers mergeTrackingId, linking anonymous history to your account.
What actually happens
You install the extension and visit any web page containing manga images.
The extension's content script is injected on all URLs.
The extension attaches a permanent UUID to every POST request it sends to its translation server.
The ismanga-visit-id header carries the same value on every request, in every tab, across every browsing session.
Captured request
Translation result returned. The UUID is logged server-side regardless of query type.
- Accept
- application/json
- Content-Type
- application/json
- Authorization
- Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6InRlc3Qif...
- ismanga-visit-id
- 196c1ba2-c14c-49cd-afda-dfbe923edb30
{ "query": "query { pollTranslationQueue(taskId: \"abc123\", isHighQuality: false, url: \"https://example-manga-site.com/chapter/1/page/5.jpg\", isPseudoUrl: false, langCode: \"en\", shouldCache: true) { code payload { x y width height } } }"}What's stored on your device
The extension's permanent device identifier. Never cleared or rotated; syncs across every Chrome profile on the same Google account.
- Location
- chrome.storage.sync key 'visitId'
{ "visitId": "196c1ba2-c14c-49cd-afda-dfbe923edb30"}The code that does this
UUID generation and persistence (content.bundle.js ~line 125139-125167)
var getOrCreateVisitId = async function(callback) { try { const stored = await chrome.storage.sync.get([VISIT_ID_KEY]); let visitId = stored[VISIT_ID_KEY]; if (!visitId) { visitId = uuidv4(); chrome.storage.sync.set({ [VISIT_ID_KEY]: visitId }); } await callback?.(visitId); } catch (err) { throw new Error('Failed to get visitId', { cause: err }); }};The code that does this
Anonymous-to-account linking on sign-in (content.bundle.js ~line 26979-26991)
var mergeTrackingId = function({ idToken, visitId }) { const body = JSON.stringify({ query: `mutation { mergeTrackingId { success } }` }); return post({ visitId, idToken, init: { body } });};// Called immediately after successful email or Google sign-in:// ur((visitId) => mergeTrackingId({ idToken, visitId }));Destinations receiving the persistent UUID
- api.ismanga.com
IsManga's GraphQL API. Receives every translation request and analytics event with the ismanga-visit-id UUID and, for signed-in users, a Firebase ID token.