Is Lingdys for Chrome safe?
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.…
Who publishes itLingit AS - no other listings under this identity
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.
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
You use a Lingdys language feature on a webpage.
The extension includes the current site hostname in the Webappella request.
- Current site hostnamedocs.google.com (illustrative)
Shows which website you were using when the language feature request was made.
- Webappella API key51c0ad386370395296b58072cd5d63bf
Identifies the extension client making the request.
- Signed-in request contextAuthorization: Bearer <redacted>
When you are signed in, the request can be associated with your extension account session.
Webappella client adds the page hostname before relaying requests
Readable equivalent
auth-iframe.jsclass 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, }); }}- 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
You type into an editable field or select text on a webpage.
The extension can send the current word, nearby text, or sentence to Webappella for language assistance.
- Current wordrecieve (illustrative)
Shows the word you are typing or checking.
- Text before the wordPlease review the payroll draft and (illustrative)
Can reveal what you were writing immediately before the checked word.
- Text after the wordbefore Monday morning (illustrative)
Can reveal nearby text from the same field or page.
- Sentence for grammar correctionThe quarterly forecast are ready for review. (illustrative)
Can include a full sentence from the page or editable field.
- Current site hostnameintranet.example.com (illustrative)
Shows which website you were using when the text request was made.
Editable-page events feed Webappella prediction and correction calls
Readable event handling
base.jsdocument.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.jsclass 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.jsclass 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, }); }}- 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
You accept a spelling or prediction correction.
The extension records the original word, replacement word, and nearby text with Lingit's text-check service.
- Original wordrecieve (illustrative)
Shows the word that was replaced.
- Replacement wordreceive (illustrative)
Shows the correction you accepted.
- Left-side text contextPlease confirm whether we can (illustrative)
Can reveal nearby text that appeared before the corrected word.
- Right-side text contextthe updated contract today (illustrative)
Can reveal nearby text that appeared after the corrected word.
- Correction typespelling correction (illustrative)
Shows whether the accepted change came from spelling or prediction assistance.
Accepted corrections become RecordAction gRPC calls
Readable correction-selection flow
base.jsasync 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.jsasync 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);}- 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