Is Lingdys for Chrome safe?

Medium risk

Lingdys is medium risk. Code analysis shows Lingdys adds the current page hostname as domainoverride on Webappella requests. Using spell-check, prediction, dictionary, grammar, or text-to-speech sends the site you're on to service.webappella.com.…

Lingit ASv0.6.5.65Chrome Web Store
55Risk
Who publishes it

Lingit AS - no other listings under this identity

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

Publisher
Lingit AS
Declared legal entity
Lingit AS
Registered address
Professor Brochs gate 2, Trondheim 7030, NO
Registered contact
Lingit AS

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

Publishers can request a review.

Findings

Current site hostname sent to Webappella

Code analysis shows Lingdys adds the current page hostname as domainoverride on Webappella requests.

Using spell-check, prediction, dictionary, grammar, or text-to-speech sends the site you're on to service.webappella.com.

Severity
Medium unwanted
Type
Unexpected
CWE
CWE-200
Source
Dynamic sandbox
What actually happens
You did this

You use a Lingdys language feature on a webpage.

The extension did this

The extension includes the current site hostname in the Webappella request.

Fields added to Webappella language-service requests
  • Current site hostname
    docs.google.com (illustrative)

    Shows which website you were using when the language feature request was made.

  • Webappella API key
    51c0ad386370395296b58072cd5d63bf

    Identifies the extension client making the request.

  • Signed-in request context
    Authorization: Bearer <redacted>

    When you are signed in, the request can be associated with your extension account session.

Captured request
POSThttps://service.webappella.com/correction/
The code that does this

Webappella client adds the page hostname before relaying requests

Readable version

Readable equivalent

auth-iframe.js
class WebappellaClient {  constructor(profileDistributorService) {    this.profileDistributorService = profileDistributorService;    this.BASE_URL = "https://service.webappella.com/";    this.API_KEY = "51c0ad386370395296b58072cd5d63bf";  }  async get(path, options = {}) {    const { params = {} } = options;    if (!params.hasOwnProperty("domainoverride") || !params.domainoverride) {      params.domainoverride = window.location.hostname;    }    const url = new URL(this.BASE_URL + path);    url.searchParams.set("apikey", this.API_KEY);    const user = this.profileDistributorService.userDistributor.get();    if (user) {      options.headers ||= {};      options.headers.Authorization = "Bearer " + user.access_token;    }    for (const key in params) {      url.searchParams.set(key, params[key]);    }    return await remoteCall(Commands.FetchDataInBackgroundGet, { url, options });  }  async post(path, options = {}) {    options.data = options.data || {};    options.data.apikey = this.API_KEY;    const user = this.profileDistributorService.userDistributor.get();    if (user) {      options.headers ||= {};      options.headers.Authorization = "Bearer " + user.access_token;    }    if (!options.data.hasOwnProperty("domainoverride") || !options.data.domainoverride) {      options.data.domainoverride = window.location.hostname;    }    return await remoteCall(Commands.FetchDataInBackgroundPost, {      url: this.BASE_URL + path,      options,    });  }}
External language-service destination
    • service.webappella.com

    Receives language-assistance requests for corrections, predictions, grammar, dictionary, and speech features.

Typed and selected text sent to Webappella

Code analysis shows Lingdys forwards typed words, nearby text, and full sentences to service.webappella.com for prediction, correction, and grammar.

The client also adds the hostname and, when signed in, an auth token.

Severity
Medium unwanted
Type
Unexpected
CWE
CWE-200
Source
Dynamic sandbox
What actually happens
You did this

You type into an editable field or select text on a webpage.

The extension did this

The extension can send the current word, nearby text, or sentence to Webappella for language assistance.

Text fields prepared for Webappella requests
  • Current word
    recieve (illustrative)

    Shows the word you are typing or checking.

  • Text before the word
    Please review the payroll draft and (illustrative)

    Can reveal what you were writing immediately before the checked word.

  • Text after the word
    before Monday morning (illustrative)

    Can reveal nearby text from the same field or page.

  • Sentence for grammar correction
    The quarterly forecast are ready for review. (illustrative)

    Can include a full sentence from the page or editable field.

  • Current site hostname
    intranet.example.com (illustrative)

    Shows which website you were using when the text request was made.

Captured request
POSThttps://service.webappella.com/prediction/
The code that does this

Editable-page events feed Webappella prediction and correction calls

Readable version

Readable event handling

base.js
document.addEventListener("mouseup", (event) => {  const editableTarget = event.target.isContentEditable ||    event.target.nodeType === "text" ||    event.target.nodeType === "textarea" ||    event.target.type === "text" ||    event.target.type === "textarea" ||    event.target.nodeName.toLowerCase() === "input";  if (!editableTarget || event.target.type === "password" || event.target.type === "number") {    this.onOutsideEvent.inform();    if ((this.bundleName === "pdf" || this.bundleName === "base") &&        event.target instanceof HTMLElement &&        event.target.tagName !== "LINGDYS-FOR-CHROME") {      this.onIframeMouseDown.inform(event);    }  } else {    this.onMouseUp.inform(event);  }}, true);document.addEventListener("keyup", (event) => {  const editableTarget = event.target.isContentEditable ||    event.target.nodeType === "text" ||    event.target.nodeType === "textarea" ||    event.target.type === "text" ||    event.target.type === "textarea" ||    event.keyCode === KeyCodes.CTRL ||    event.target.nodeName.toLowerCase() === "input";  if (!editableTarget || event.target.type === "password") {    this.onOutsideEvent.inform();  } else {    this.onKeyUp.inform(event);  }}, true);

Readable language API

auth-iframe.js
class LanguageAssistanceApi {  constructor(webappella) {    this.webappella = webappella;  }  getWordPredictions(language, currentWord, precedingText, succeedingText) {    const data = {};    data.CurrentWord = currentWord !== undefined ? normalize(currentWord) : "";    data.PrecedingText = precedingText !== undefined ? normalize(precedingText) : "";    data.SucceedingText = succeedingText !== undefined ? normalize(succeedingText) : "";    data.Language = language;    data.NumberOfSuggestions = 9;    return this.webappella.post("prediction/", { data, json: true });  }  getWordCorrections(language, currentWord, precedingText) {    const data = {};    data.CurrentWord = currentWord !== undefined ? normalize(currentWord) : "";    data.PrecedingText = precedingText !== undefined ? normalize(precedingText) : "";    data.SucceedingText = "";    data.Language = language;    data.NumberOfSuggestions = 9;    return this.webappella.post("correction/", { data, json: true }).then((response) => {      response.ShowPredictionHotKeys = true;      response.isEnabled = true;      return response;    });  }  getSentenceCorrection(language, sentence) {    return this.webappella.post("grammar/sentenceCorrection/", {      data: { Language: language, Sentence: sentence || "" },      json: true,    });  }}

Readable Webappella client

auth-iframe.js
class WebappellaClient {  constructor(profileDistributorService) {    this.profileDistributorService = profileDistributorService;    this.BASE_URL = "https://service.webappella.com/";    this.API_KEY = "51c0ad386370395296b58072cd5d63bf";  }  async get(path, options = {}) {    const { params = {} } = options;    if (!params.hasOwnProperty("domainoverride") || !params.domainoverride) {      params.domainoverride = window.location.hostname;    }    const url = new URL(this.BASE_URL + path);    url.searchParams.set("apikey", this.API_KEY);    const user = this.profileDistributorService.userDistributor.get();    if (user) {      options.headers ||= {};      options.headers.Authorization = "Bearer " + user.access_token;    }    for (const key in params) {      url.searchParams.set(key, params[key]);    }    return await remoteCall(Commands.FetchDataInBackgroundGet, { url, options });  }  async post(path, options = {}) {    options.data = options.data || {};    options.data.apikey = this.API_KEY;    const user = this.profileDistributorService.userDistributor.get();    if (user) {      options.headers ||= {};      options.headers.Authorization = "Bearer " + user.access_token;    }    if (!options.data.hasOwnProperty("domainoverride") || !options.data.domainoverride) {      options.data.domainoverride = window.location.hostname;    }    return await remoteCall(Commands.FetchDataInBackgroundPost, {      url: this.BASE_URL + path,      options,    });  }}
External language-service destination
    • service.webappella.com

    Receives language-assistance requests for corrections, predictions, grammar, dictionary, and speech features.

Accepted correction details sent to Lingit

Code analysis shows accepting a correction in Lingdys sends the event to Lingit's text-check service: the original word, the replacement, and up to 100 characters of nearby text on each side.

Severity
Medium unwanted
Type
Unexpected
CWE
CWE-200
Source
Dynamic sandbox
What actually happens
You did this

You accept a spelling or prediction correction.

The extension did this

The extension records the original word, replacement word, and nearby text with Lingit's text-check service.

Fields placed into the RecordAction request
  • Original word
    recieve (illustrative)

    Shows the word that was replaced.

  • Replacement word
    receive (illustrative)

    Shows the correction you accepted.

  • Left-side text context
    Please confirm whether we can (illustrative)

    Can reveal nearby text that appeared before the corrected word.

  • Right-side text context
    the updated contract today (illustrative)

    Can reveal nearby text that appeared after the corrected word.

  • Correction type
    spelling correction (illustrative)

    Shows whether the accepted change came from spelling or prediction assistance.

Captured request
POSThttps://textcheck.lingitapis.com/lingitapis.textcheck.v1alpha1.TextCheckService/RecordAction
The code that does this

Accepted corrections become RecordAction gRPC calls

Readable version

Readable correction-selection flow

base.js
async onCorrectionSelected(context, suggestion) {  this.hideAllWindows();  if (this.lingitAPI.recordAction) {    this.lingitAPI.recordAction({      actionTime: new Date(),      type: ActionKind.ACCEPTED,      suggestionType: SuggestionType.SPELLING,      contextType: ContextType.LINE,      leftContext: context.caretContextWithOffset.text.slice(0, context.caretContextWithOffset.wordStartOffset),      rightContext: context.caretContext.text.slice(context.caretContext.wordEndOffset),      newValue: suggestion.Text,      oldValue: context.caretContextWithOffset.word,    });  }  if (context.offset) {    await this.helper.simulateKeyPress(KeyCodes.LEFT_ARROW, { numberOfKeystrokes: context.offset });  }  const wordsToReplace = suggestion.LookUpType === "DoubleWord" ? 2 : 1;  let selectionLength = 0;  if (context.caretContextWithOffset.word) {    selectionLength = context.caretContextWithOffset.word.length;    await this.textEditor.moveCaretToWordBegin();  }  for (let index = 1; index < wordsToReplace; index++) {    selectionLength += await this.textEditor.moveCaretToPreviousWordBegin();  }  await this.helper.selectText(selectionLength);  await this.helper.insertText(suggestion.Text);}async onPredictionSelected(context, suggestion) {  this.hideAllWindows();  if (this.lingitAPI.recordAction) {    this.lingitAPI.recordAction({      actionTime: new Date(),      type: ActionKind.ACCEPTED,      suggestionType: SuggestionType.PREDICTION,      contextType: ContextType.LINE,      leftContext: context.caretContext.text.slice(0, context.caretContext.wordStartOffset),      rightContext: context.caretContext.text.slice(context.caretContext.wordEndOffset),      newValue: suggestion.Text,      oldValue: context.caretContext.word,    });  }}

Readable RecordAction construction

auth-iframe.js
async recordAction(language, actionDetails) {  const request = new RecordActionRequest();  request.setTextChecker(this.getTextChecker(language).name);  let leftContext;  let rightContext;  let leftLines = actionDetails.leftContext.split("");  let rightLines = actionDetails.rightContext.split("");  leftContext = leftLines.length > 1 ? "" + leftLines.slice(-1) : leftLines[0];  rightContext = rightLines.length > 1 ? rightLines[0] + "" : rightLines[0];  if (leftContext.length > 100) {    leftLines = leftContext.slice(-100).split(" ");    leftLines.shift();    leftContext = leftLines.join(" ");  }  if (rightContext.length > 100) {    rightLines = rightContext.substring(0, 99).split(" ");    rightLines.pop();    rightContext = rightLines.join(" ");  }  const action = new Action();  action.setActionTime();  action.setKind(actionDetails.type);  action.setIssueKind(actionDetails.suggestionType);  action.setNewValue(actionDetails.newValue);  action.setMatchedString(actionDetails.oldValue);  action.setContextType(actionDetails.contextType);  action.setLeftContext(leftContext);  action.setRightContext(rightContext);  request.setAction(action);  const metadata = { deadline: String(Date.now() + this.deadlineMs) };  return this.textCheckClient.recordAction(request, metadata);}
External text-check destination
    • textcheck.lingitapis.com

    Receives gRPC recordAction calls for accepted spelling and prediction corrections.

What it can do

Permissions this extension asks for, as declared in version 0.6.5.62. Asking for a permission is not a finding on its own - it is what the extension can do if it chooses to. The current listed version is 0.6.5.65, which we have not unpacked yet.

  • Read and change your data on every site you visit

    http://*/* and 2 more

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

    https://*/*

  • Block and redirect the requests your browser makes

    declarativeNetRequest

  • See which of your requests its blocking rules matched

    declarativeNetRequestFeedback

  • See, disable and uninstall your other extensions, including your security ones

    management

  • Store data in your browser

    storage

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

    tabs

  • Write to your clipboard

    clipboardWrite

  • Read whatever you have copied to your clipboard

    clipboardRead

  • Capture the contents of your screen, window or a tab

    desktopCapture

  • Watch every request your browser makes

    webRequest

  • Detect when you step away from your computer

    idle

  • Run hidden pages in the background

    offscreen

  • Run its own code inside the pages you visit

    scripting

  • See every page you navigate to, as you navigate to it

    webNavigation

  • Show you desktop notifications

    notifications

declarativeNetRequestWithHostAccess
Updated 30 September 2026ilglobbeckliiacnbnohbbefhkoloblo