Is DeepL: translate and write with AI safe?

Medium risk

DeepL attaches a persistent installation ID and browser fingerprint to every analytics event it sends to its servers.

Each time the user interacts with the extension, a base analytics payload is assembled containing an installationId (a UUID stored in chrome.storage.sync that survives reinstalls), a sessionId, browserInstanceId, browser name, version, platform, language, and userAgent string. When the user is signed in, accountId and organizationId are added. A dapUid cookie read from deepl.com is included as webInstanceId, linking extension events to the user's DeepL website activity. All of this is transmitted to s.deepl.com/chrome/statistics with each analytics call.

team-browser-extensionsv1.99.0Chrome Web Store
45Risk
Who publishes it

DeepL - no other listings under this identity, 3 shared hostnames

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

Publisher
team-browser-extensions
Declared legal entity
DeepL
Registered address
Maarweg 165, Köln 50825, DE
Registered contact
DeepL

Shared hosts - 3 hostnames

Hostnames hardcoded in this extension that few other listings call. That can mean one operator behind both, and it can equally mean a small shared vendor, so it is context rather than a conclusion. Hosts that many listings call are left out: they are services, not connections.

deepl.qualtrics.com
Also called by 3 other listings, including DeepL, DeepL: AI translator and writing assistant
jira.deepl.dev
Also called by 3 other listings, including DeepL, DeepL: AI translator and writing assistant
www-app.linguee.com
Also called by 3 other listings, including DeepL, DeepL: AI translator and writing assistant

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

Publishers can request a review.

Findings

SeverityMEDIUM
ClassUNWANTED
TypeUnexpected
CWECWE-359
SourceAI SANDBOX

Persistent Installation ID Fingerprints Every Analytics Event

Every DeepL action, translating, opening the popup, changing settings, POSTs to s.deepl.com/chrome/statistics with a persistent installationId synced across devices, plus a device UUID, user-agent, platform.

Signed-in adds accountId.

01EvidenceCAUSE EFFECT
What actually happens
You did this

You translate text, open the DeepL popup, or take any other action inside the extension.

The extension did this

The extension POSTs to s.deepl.com/chrome/statistics with a persistent install UUID, a device UUID, your user-agent, platform, language and, if signed in, your DeepL account ID.

If you have visited deepl.com in this browser, a dapUid cookie value is also attached, linking your extension events to your website session.

02EvidenceNETWORK CAPTURE
Captured request
POSThttps://s.deepl.com/chrome/statistics
HTTP 200 OK, empty body or acknowledgement JSON
Headers
Acceptapplication/json
Content-Typeapplication/json
Body
{
  "eventId": 3,
  "extensionVersion": "1.72.0",
  "sessionId": "b2f1c3d4-7e8a-4b9c-a0d1-2e3f4a5b6c7d",
  "sessionType": 2,
  "installationId": "1aca7a42-9c0a-4e64-adc3-3052987062ec",
  "browserInstanceId": "70e3f0aa-491a-4b3f-9457-09a55f5d4df5",
  "originStore": "chrome",
  "userInfos": {
    "userType": 3,
    "translatorServiceType": 0,
    "apiServiceType": 0,
    "docTranslatorServiceType": 0,
    "writeServiceType": 0
  },
  "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36",
  "platform": "Linux",
  "browser": "Chrome",
  "browserVersion": "146.0.0.0",
  "browserLang": "en-GB",
  "clientExperiments": []
}
03EvidenceFIELD TABLE
Fields present in every analytics POST to s.deepl.com/chrome/statistics:
FieldValueWhy it matters
Your installation ID
1aca7a42-9c0a-4e64-adc3-3052987062ecA UUID assigned at install and stored in synced browser storage, following you across every device where your Chrome profile is signed in.
Your browser instance ID
70e3f0aa-491a-4b3f-9457-09a55f5d4df5A second UUID stored locally in the browser. Identifies this specific browser installation independently of your account.
Session ID
b2f1c3d4-7e8a-4b9c-a0d1-2e3f4a5b6c7dA UUID generated at the start of each browser session. Refreshed every 30 minutes of inactivity.
Your DeepL account ID
usr_8d4f2a1c9e7b3d5fSent when you are signed into a DeepL account. Links all events to your registered identity.
Website session link (dapUid)
dap_a9f3b2c1d4e5f678The dapUid cookie value from deepl.com, attached as webInstanceId, connecting extension activity to your deepl.com browsing history.
Full user-agent string
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36Your browser's complete user-agent, including OS version, browser version, and rendering engine details.
Platform and browser language
Linux / en-GBYour operating system platform and the language your browser is configured to use.
Extension version
1.72.0Which version of the DeepL extension you have installed. Consistent across all events.
04EvidenceCODE COMPARE
The code that does this

The base analytics event assembly, every analytics call goes through this function.

What it actually does
Hy() — base analytics event assembly and POST
// Called before every analytics event. Assembles a base event object
// with persistent identifiers, browser fingerprint, and account info,
// then POSTs it to s.deepl.com/chrome/statistics.
async function buildAndSendAnalyticsEvent(eventCustomizer) {
  let installationId, sessionId, browserInstanceId, accountId, organizationId;
  let userType, sessionType, translatorServiceType;

  // Read persistent UUIDs from storage
  const settings = await readFromSyncStorage(["session", "installationId", ...]);
  sessionId    = settings.session.id;     // refreshed every 30 min
  installationId = settings.installationId; // permanent, synced across devices

  browserInstanceId = await readBrowserInstanceId(); // from chrome.storage.local
  accountId         = await readAccountId();          // DeepL account if logged in

  const browserInfo = getBrowserInfo(); // userAgent, platform, browser, browserVersion, browserLang
  const dapUidCookie = await chrome.cookies.get({ name: 'dapUid', url: 'https://www.deepl.com' });
  const webInstanceId = dapUidCookie ? dapUidCookie.value : undefined;

  // Base payload attached to EVERY analytics event
  const baseEvent = {
    eventId: undefined,          // overwritten by each specific event
    extensionVersion: '1.72.0',
    sessionId,
    sessionType,
    installationId,              // persistent UUID from install
    browserInstanceId,           // second persistent UUID
    originStore: getOriginStore(chrome.runtime.id),
    userInfos: {
      ...(accountId && { accountData: { accountId, ...(organizationId && { organizationId }) } }),
      userType,
      translatorServiceType,
      ...
    },
    userAgent:     browserInfo.userAgent,
    platform:      browserInfo.platform,
    browser:       browserInfo.browser,
    browserVersion: browserInfo.browserVersion,
    browserLang:   browserInfo.browserLang,
    clientExperiments: await getExperiments(),
    ...(webInstanceId && { webInstanceId }), // deepl.com cookie value
  };

  const event = eventCustomizer ? eventCustomizer(baseEvent) : baseEvent;

  return fetch('https://s.deepl.com/chrome/statistics', {
    method: 'POST',
    headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' },
    body: JSON.stringify(event),
    credentials: 'omit',
  });
}
05EvidenceSTORAGE DUMP
What's stored on your device

Assigned once at install, synced to every device on your profile. Attached to every analytics POST, linking all usage to one ID.

Locationchrome.storage.sync key 'installationId'
Contents
1aca7a42-9c0a-4e64-adc3-3052987062ec
06EvidenceTHIRD PARTY LIST
Where analytics data is sent:
  • s.deepl.com

    Receives every analytics event as a JSON POST to /chrome/statistics. Operated by DeepL SE (Cologne, Germany). No third-party sharing is described for this endpoint.

SeverityMEDIUM
ClassUNWANTED
TypeUnexpected
CWECWE-359
SourceAI SANDBOX

Translation requests send site context to DeepL analytics

Source-code analysis shows a completed translation builds an analytics event for DeepL's statistics endpoint: hostname, installation/session IDs, user type, language direction, character count and trigger.

Traffic tests couldn't capture it.

01EvidenceCAUSE EFFECT
What actually happens
You did this

You request a translation from the extension.

This can be inline text translation or another translation flow that completes through the service worker.

The extension did this

The extension prepares a translation analytics event for DeepL.

The event combines the page hostname with installation, session, browser, language, and character-count fields.

02EvidenceFIELD TABLE
Fields placed in the translation analytics event
FieldValueWhy it matters
Page hostname
de.wikipedia.orgShows which website was open when you translated text.
Installation ID
d154b8e5-5f28-434b-beae-e816fa0d50b3Lets DeepL associate translation events from the same browser extension install.
Session ID
session-20260712-8f4b2cGroups activity from the same browser session.
User type
free accountDescribes whether the extension sees the account as anonymous, free, or paid.
Translation details
German to English, 240 characters, inline translationRecords the language direction, amount of text, and how the translation was started.
03EvidenceNETWORK CAPTURE
Captured request
POSThttps://s.deepl.com/chrome/statistics
Headers
Acceptapplication/json
Content-Typeapplication/json
04EvidenceCODE COMPARE
The code that does this

The content script supplies the hostname and the service worker posts the analytics event

What it actually does
Content script translation messagebuild/content.js
EA.update({
  translationState: v.ongoingTranslation,
  translatedSnippet: "",
  originalSnippet: a,
  itaTranslateTrigger: n ? C : E
}), wH({
  type: ty,
  payload: {
    sourceText: a,
    targetLanguage: t,
    domainName: window.location.hostname,
    trigger: n ? C : E,
    websiteLanguage: e.websiteData.websiteLanguage
  }
})
Translation analytics wrapperbackground.js
function $b(event) {
  return r(this, void 0, void 0, function*() {
    return Gb(base => Object.assign(Object.assign({}, base), {
      eventId: BS.EVENT_ID_TRANSLATION_REQUEST,
      translationRequestData: Object.assign({
        extensionVersion: "1.93.0",
        domainName: event.domainName,
        userId: event.installationId,
        translationTrigger: tE(event.trigger),
        translationData: {
          sourceLang: event.websiteLanguage,
          targetLang: event.targetLanguage,
          sourceLength: event.characterCount
        }
      }, event.translationPerformance && { translationPerformance: event.translationPerformance }, {
        nicheIntegration: TE(event.nicheIntegration || Mn)
      })
    }));
  });
}
Statistics endpoint senderbackground.js
function Gb(buildEvent) {
  return r(this, void 0, void 0, function*() {
    const browser = Uo();
    const clientExperiments = yield cR();
    const baseEvent = Object.assign({
      eventId: void 0,
      extensionVersion: "1.93.0",
      sessionId: sessionId,
      installationId: installationId,
      browserInstanceId: browserInstanceId,
      userAgent: browser.userAgent,
      platform: browser.platform,
      browser: browser.browser,
      browserVersion: browser.browserVersion,
      browserLang: browser.browserLang,
      clientExperiments
    }, webInstanceId && { webInstanceId });
    const event = buildEvent ? buildEvent(baseEvent) : baseEvent;
    return fetch("https://s.deepl.com/chrome/statistics", {
      method: "POST",
      headers: { Accept: "application/json", "Content-Type": "application/json" },
      body: JSON.stringify(event),
      credentials: "omit"
    });
  });
}
05EvidenceTHIRD PARTY LIST
Destination contacted by this code path
  • s.deepl.com

    DeepL statistics endpoint receiving translation analytics JSON.

SeverityMEDIUM
ClassUNWANTED
TypeUnexpected
CWECWE-200
SourceAI SANDBOX

Selected page text is sent to DeepL translation endpoints

Source-code analysis shows that translating selected, input, or page text has the content script collect it and pass it to the service worker, which forwards it to DeepL's JSONRPC path.

Traffic tests didn't capture a request.

01EvidenceCAUSE EFFECT
What actually happens
You did this

You select text or ask the extension to translate page content.

The extension needs the source text to produce the translation result.

The extension did this

The extension sends the selected or collected page text to DeepL's translation service.

The request includes language settings and the page hostname.

02EvidenceFIELD TABLE
Fields in the translation request path
FieldValueWhy it matters
Selected or page text
Guten Tag, bitte ubersetzen Sie diesen Absatz.Contains the text you asked the extension to translate, which may include personal or work content if that text is on the page.
Page hostname
de.wikipedia.orgShows which website the translated text came from.
Target language
EN-USShows the language you requested for the translation result.
Character count
240 charactersRecords how much text was included in the translation request.
03EvidenceNETWORK CAPTURE
Captured request
POSThttps://www2.deepl.com/jsonrpc?client=chrome-extension,1.93.0
Headers
Content-Typeapplication/json
04EvidenceCODE COMPARE
The code that does this

The content script collects text and the service worker forwards it for translation

What it actually does
Content script request payloadbuild/content.js
const y = wj(p.map(e => e.text));
v = o ? [yield kn({
  action: fn.dlRequestImproveWriting,
  payload: Object.assign(Object.assign({
    requests: y.simplifiedHtml.map(e => ({ text: e })),
    domainName: window.location.hostname,
    websiteLanguage: f,
    sourceLang: a
  }, s && { isHtml: !0 }), { nicheIntegration: i })
})] : yield kn({
  action: yn.dlRequestInputTranslation,
  payload: {
    requests: y.simplifiedHtml.map(e => ({ text: e })),
    domainName: window.location.hostname,
    websiteLanguage: f,
    targetLang: r,
    platformBehavior: i
  }
});
Service worker translation handlerbackground.js
const QL = request => r(void 0, void 0, void 0, function*() {
  return yield request.payload.isWriteRequest ? JL(request) : (request => r(void 0, [request], void 0, function*({ payload: { domainName, requests, targetLang, websiteLanguage, formality, platformBehavior, skipDapReporting } }) {
    yield $N();
    const { installationId, targetLanguageUserInput } = yield MN(["targetLanguageUserInput", "installationId"]);
    const selectedTargetLanguage = targetLang || targetLanguageUserInput;
    const response = yield DL({ sourceTexts: requests.map(e => e.text) || [], selectedTargetLanguage, formality });
    const translatedTexts = response.processedTranslation.map(e => e.text);
    const detectedSourceLanguage = response.processedTranslation[0].detected_source_language;
    skipDapReporting || PN(() => $b({
      installationId,
      trigger: platformBehavior !== Mn ? D : C,
      websiteLanguage: detectedSourceLanguage,
      targetLanguage: selectedTargetLanguage,
      characterCount: requests.reduce((sum, item) => sum + item.text.length, 0),
      domainName,
      nicheIntegration: platformBehavior || Mn,
      glossaryTrackingInfo: response.glossaryTrackingInfo
    }));
    return [{ detected_source_language: detectedSourceLanguage, text: translatedTexts.flat().join(""), texts: translatedTexts }];
  }))(request);
});
05EvidenceTHIRD PARTY LIST
Translation service destinations in the shipped configuration
  • www2.deepl.com

    DeepL free translation JSONRPC endpoint for extension translation requests.

  • api.deepl.com

    DeepL Pro translation JSONRPC endpoint for extension translation requests.

+1 more finding not shown

Where it sends data

Destinations our analysis observed DeepL 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.

  • s.deepl.com

    DeepL sends data to s.deepl.com. No other extension we have analysed sends data here.

Updated 30 September 2026cofdbpoegempjloogbagkncekinflcnj