Is 欧路翻译 - 沉浸网页划词翻译工具 safe?

Medium risk

欧路翻译 is medium risk. When you browse to a site matching the rule list, the extension fetches page rules from api.frdic.com and applies CSS from them to matching elements. Dynamic analysis observed a rule for m.youtube.com; the content script runs on all URLs.…

EUSOFTv26.8.0Chrome Web Store
45Risk

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

Publishers can request a review.

Findings

SeverityMEDIUM
ClassUNWANTED
TypeUnexpected
CWECWE-829
SourceAI SANDBOX

Server-provided CSS can change matched webpages

When you browse to a site matching the rule list, the extension fetches page rules from api.frdic.com and applies CSS from them to matching elements.

Dynamic analysis observed a rule for m.youtube.com; the content script runs on all URLs.

01EvidenceCAUSE EFFECT
What actually happens
You did this

You open a webpage where the extension's content script is active.

The manifest grants the content script access to all URL patterns listed in the extension package.

The extension did this

The extension checks server-provided page rules and can apply CSS when the current page matches.

The rule engine uses match patterns, selectors, global style strings, and injected CSS strings.

02EvidenceFIELD TABLE
Fields in the page-rule configuration
FieldValueWhy it matters
Matched website
m.youtube.comLets the rule decide which sites can receive page changes while you browse.
Element selector
.comment-textLets the rule pick which parts of the page can be restyled.
Element CSS
max-height:unset;Lets the rule add style text directly to matching page elements.
Injected page CSS
eudic-chrome-extension-page-styleLets the rule add a style element that affects the matched page.
03EvidenceNETWORK CAPTURE
Captured request
GEThttps://api.frdic.com/api/v2/appsupport/ExtensionConfig
Observed response included ExtensionConfig rules for m.youtube.com with globalStyles CSS and an injectedCss array.
Headers
Content-Typeapplication/json
04EvidenceCODE COMPARE
The code that does this

The shipped rule fetch and CSS application paths

What it actually does
Fetches ExtensionConfig from api.frdic.commain/8079.js
apiGetExtensionRule() {
  const t = `${(0,s.uh)()}/api/v2/appsupport/ExtensionConfig`;
  return this.fetchApi(t, {
    method: "GET",
    headers: {
      Authorization: (0, s.jL)("", ""),
      "Content-Type": "application/json"
    }
  }, void 0, !0);
}
Matches the page and applies CSScontent/main.js
fetchRulesForPage() {
  const e = this.getCurrentUrl();
  let o;
  return this.extensionRule && (o = this.extensionRule.rules.find(u => {
    if (u.matches && 0 !== u.matches.length) return this.getMatchedUrl(e, u.matches);
  })), o;
}
prepareActionForPage() {
  if (this.isActionForPagePrepared) return;
  if (this.isActionForPagePrepared = !0, this._doc.querySelector("div.ql-editor[contenteditable='false']")) {
    const s = this._doc.body.innerHTML;
    this._doc.body.innerHTML = "", this._doc.body.innerHTML = s;
  }
  for (const s of this.commonExcludeSelectors) {
    const u = this._doc.querySelectorAll(s);
    for (let h = 0; h < u.length; h++) {
      const g = u[h];
      if (g instanceof HTMLElement) {
        const v = g.getAttribute(nt.h.kEusoftTranslateIgnoreAttribute);
        (!v || "1" !== v) && g.setAttribute(nt.h.kEusoftTranslateIgnoreAttribute, "1");
      }
    }
  }
  const o = this.fetchRulesForPage();
  if (o) {
    if (o.globalStyles) for (const s in o.globalStyles) {
      const u = this._doc.querySelectorAll(s);
      for (let h = 0; h < u.length; h++) {
        const g = u[h];
        if (g instanceof HTMLElement) {
          const v = g.getAttribute(nt.h.kEusoftTranslateDidAddGlobalStyleAttribute);
          (!v || "1" !== v) && (g.setAttribute(nt.h.kEusoftTranslateDidAddGlobalStyleAttribute, "1"), g.style.cssText = g.style.cssText + o.globalStyles[s]);
        }
      }
    }
    if (o.injectedCss) {
      let s;
      if (s = Array.isArray(o.injectedCss) ? o.injectedCss.join("\n") : o.injectedCss, !document.getElementById(nt.h.pageInjectedStyleId)) {
        const u = document.createElement("style");
        u.textContent = s, u.id = nt.h.pageInjectedStyleId, document.head.appendChild(u);
      }
    }
  }
}
05EvidenceTHIRD PARTY LIST
Remote host involved in the rule path
  • api.frdic.com

    Receives the ExtensionConfig request and returns page-rule configuration used by the extension.

SeverityMEDIUM
ClassUNWANTED
TypeUnexpected
CWECWE-359
SourceAI SANDBOX

Selected text context is sent for translation

When you select text on a webpage, the content script computes nearby text as context and sends it to Eudic translation endpoints with the word.

Captured traffic lacked a body, but the shipped code places word and context into requests.

01EvidenceCAUSE EFFECT
What actually happens
You did this

You select text on a webpage to use the translation feature.

The content script listens for selection-driven capture flows on the pages where it runs.

The extension did this

The extension collects the selected text and nearby page context for translation.

The context helper can return up to 1,000 characters around the selected text.

02EvidenceFIELD TABLE
Fields passed to translation requests
FieldValueWhy it matters
Selected text
quarterly revenueSends the word or phrase you selected so the service can translate it.
Nearby page text
The quarterly revenue <b>increased</b> after the product launch.Can include surrounding text from the same page section, not just the exact word you selected.
Language setting
lang=enTells the service which language direction to use for your translation.
Request type
type=webMarks the translation request as coming from the web flow.
03EvidenceNETWORK CAPTURE
Captured request
POSThttps://dict.eudic.net/dicts/minitranslate
Headers
Content-Typeapplication/x-www-form-urlencoded
04EvidenceNETWORK CAPTURE
Captured request
GEThttps://api.frdic.com/api/v2/dicts/explain
Headers
Content-Typeapplication/json
05EvidenceCODE COMPARE
The code that does this

The selection context and translation request paths

What it actually does
Builds up to 1,000 characters of context around selected textcontent/main.js
getSelectionTextFullContent(e) {
  if (!e || 0 === e.rangeCount) return "";
  const o = 1e3, s = e.getRangeAt(0), u = e.toString().trim();
  if (!u) return "";
  let h = s.commonAncestorContainer;
  h.nodeType === Node.TEXT_NODE && (h = h.parentElement);
  let v = this.collectInlineText(h).trim();
  if (v.length <= u.length) {
    let me = 0;
    for (; me < 5 && h.parentElement && (h = h.parentElement, v = this.collectInlineText(h).trim(), !(v.length > u.length)); ) me++;
  }
  if (v.length <= u.length && (v = this.getTextContent(h, !1, !0) || "", v = v.trim()), !v || 0 === v.trim().length) return u;
  const S = v.indexOf(u);
  if (-1 === S) return v.length > o ? v.substring(0, o) : v;
  const L = "\u3002\uff01\uff1f.!?\uff1b;";
  let Y = S;
  for (; Y > 0 && !L.includes(v[Y - 1]); ) Y--;
  let W = S + u.length;
  for (; W < v.length && !L.includes(v[W]); ) W++;
  W < v.length && W++;
  let K = v.substring(Y, W);
  const oe = S - Y, de = K.substring(0, oe) + "<b>" + K.substring(oe, oe + u.length) + "</b>" + K.substring(oe + u.length);
  return de.length > o ? de.substring(0, o) : de;
}
Passes selected text and context to the UI statecontent/main.js
showCaptureWindow(e, o) {
  var s = this;
  return (0, I.A)(function*() {
    if (e.currentTarget instanceof Document) {
      const h = e.currentTarget.location.href;
      if (h.startsWith((0, fe.Bw)()) || window.top !== window.self && h.startsWith((0, fe.DP)())) return;
    }
    const u = s.getCaptureSelectionModel();
    if (u) {
      const h = u.selection, g = u.selectedText;
      if (h.focusNode instanceof HTMLElement && h.focusNode.tagName.toLowerCase().startsWith(nt.h.selectorId)) return;
      if (g.length > 0) {
        if (yield s.bridgeWorkerService.checkSidePanelOpened()) {
          const W = s.getSelectionTextContext(h, g);
          return void s.bridgeWorkerService.doSidePanelTransWord(g, W).subscribe();
        }
        let L, Y = !0;
        o && e instanceof MouseEvent && (L = new ol(e.clientX, e.clientY), Y = !(L.y > o.y && L.y - o.y > 20)), s.finalShowExplainContent(g, s.getSelectionTextContext(h, g), new al(h, s.settingModel.model.captureExplainSizeWidth, s.settingModel.model.captureExplainSizeMaxHeight, void 0, L, Y));
      } else s.clearAndCancelCaptureWindow();
      s.cdr.detectChanges();
    }
  })();
}
finalShowExplainContent(e, o, s) {
  (0, i.naY)() && console.log(`finalShowExplainContent word = ${e}`), (0, fe.P)() ? Aa.A.showExplainContent(e, o, s) : (this.getInitData(), this.selectionMousePos = s, this.selectedWord = e, this.selectedContext = o, (0, fe.IY)() && (this.isDragNavBarVisible = !0));
}
getSelectionTextContext(e, o) {
  if (!e && this.doc.getSelection && (e = this.doc.getSelection()), !e) return;
  const s = this.domService.getSelectionTextFullContent(e);
  if (!s) return;
  if (s.includes(o)) return s;
  const u = o.replace(/\n/g, ""), h = s.replace(/\n/g, "");
  return h.includes(u) ? h : void 0;
}
Sends selected text and context to Eudic endpointsmain/8079.js and main/9591.js
apiExplainContent(t, e, n) {
  const r = `${(0,s.uh)()}/api/v2/dicts/explain`, o = new URLSearchParams;
  o.append("word", t), e && e.length > 0 && o.append("context", e), o.append("lang", (0, s.ZO)()), o.append("type", "web");
  const i = `${r}?${o.toString()}`;
  return this.fetchApi(i, {
    method: "GET",
    headers: {
      Authorization: (0, s.jL)(n ? n.userid : "", n && n.token ? n.token.token : ""),
      "Content-Type": "application/json"
    }
  }, void 0, !0);
}
explainWithContextResponse(t, e, n) {
  return (0, g.A)(function*() {
    return yield fetch(`${(0,s.nu)()}/dicts/ExplainWithContextStream?context=${encodeURIComponent(e)}&word=${encodeURIComponent(t)}&lang=${(0,s.ZO)()}&userid=${n}`, {
      method: "POST",
      headers: {
        EudicUserAgent: (0, s.N1)(),
        "Content-Type": "application/json"
      },
      credentials: "omit",
      body: void 0
    });
  })();
}
translate(J, P, G, H, tt, z, at) {
  const U = {
    q: J,
    from: this.eusoftTranslateMap[H],
    to: this.eusoftTranslateMap[tt] || tt,
    engine: z
  };
  P && P.length > 0 && (U.context = P), G && G.length > 0 && (U.summary = G);
  let x = "/dicts/minitranslate";
  z === K.gpt && at && (x = "/dicts/MiniTranslateStream");
  const it = new URLSearchParams(U), et = (0, b.nu)() + x, nt = this.httpWorker.fetchApiWithCookie(et, {
    method: "POST",
    headers: {
      "Content-Type": "application/x-www-form-urlencoded"
    },
    body: it
  }, !1).pipe((0, Q.n)(q => {
    let $;
    try {
      $ = JSON.parse(q);
    } catch {
      return (0, V.$)(() => new W.A(W.O.needLogin));
    }
    let T = "";
    switch (z) {
      case K.eusoft:
        for (let O of $.trans_result.data) T += O.dst + "\n";
        T = T.trim();
        break;
      case K.gpt: {
        const O = $;
        if (O.code !== d.KK.success) throw O.code === d.KK.notPurchase ? new W.A(W.O.needVip) : O.code === d.KK.noBalance ? new W.A(W.O.noBalance) : O.message;
        T = O.data.result;
      }
    }
    return (0, E.of)(T);
  }));
  return C.k.getIsLogin().pipe((0, Q.n)(q => q ? nt : (0, V.$)(() => new W.A(W.O.needLogin))));
}
06EvidenceTHIRD PARTY LIST
Translation hosts receiving selected-text context
  • dict.eudic.net

    Receives minitranslate and contextual translation requests for selected text.

  • api.frdic.com

    Receives dictionary explain requests that include the selected word and context query parameters.

Updated 17 September 2026djbfechcnkppbknmlhfcaoifgnicolin