Is Linguix: Grammar Checker and AI Writing App safe?

Medium risk

Linguix sends text fragments surrounding grammar corrections to Amplitude analytics on any website.

When a user accepts a grammar or writing suggestion, Linguix captures up to 100 characters of surrounding text (the 'context' field) along with the rule triggered and the replacement made. This data is transmitted to Amplitude via its HTTP API using a hardcoded project key. The user's Linguix account ID or a generated UUID is attached to each event as the Amplitude userId.

Linguixv1.6.157Chrome Web Store
45Risk
Who publishes it

Textly Inc. - 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
Linguix
Declared legal entity
Textly Inc.
Registered address
3479 NE 163rd St Unit #2088, North Miami Beach, FL 33160-4426, US
Registered contact
Linguix.com

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

Typed writing context is sent to Amplitude after accepted suggestions

MediumunwantedCWE-359Dynamic sandbox

When you accept a Linguix suggestion, the extension logs a ReplacementClicked event to Amplitude.

What actually happens

You did this

You accept a suggested writing replacement on a webpage.

This can happen inside forms or editable page content where the extension is active.

The extension did this

The extension logs an analytics event that includes the nearby writing context.

The event is named ReplacementClicked and is routed through the Amplitude SDK.

Fields passed into the ReplacementClicked analytics event

  • Rule that fired
    MORFOLOGIK_RULE_EN_US (illustrative)

    Shows which writing rule matched the text you were editing.

  • Nearby typed text
    Please reviev the onboarding notes before Friday. (illustrative)

    Can reveal part of what you were writing on the page when you accepted the suggestion.

  • Replacement text
    review (illustrative)

    Shows the corrected word or phrase you chose from the suggestion popover.

  • Analytics user identifier
    8f2c6b7e-41a6-4e28-9e5f-1d43d6a9c2b4 (illustrative)

    Lets the analytics provider link the event to the same extension account or stored browser profile over time.

Captured request

POSThttps://api2.amplitude.com/2/httpapi

The code that does this

The accepted replacement is converted into an Amplitude analytics event

Readable version

Content script emits the replacement details

ext-content/main.js
replace(t, n) {  this.prevent(n), !this.authUser.actionRequired() || this.authUser.actionRequired() !== z.TRIAL_ENDED || this.authUser.isPremium() ? (this.authUser.actionRequired() && this.authUser.actionRequired() === z.TRIAL_ENDS_SOON && this.alert.isPremium && chrome.storage.local.set({    [z0]: this.premiumReplacesDoneToday + 1  }), this.checkable.replace(this.alert.id, t), this.auth.isGuest() && chrome.storage.local.get("guestReplacementCount").then(a => {    chrome.storage.local.set({      guestReplacementCount: (a.guestReplacementCount || 0) + 1    })  }), chrome.storage.local.get("totalReplacementCount").then(a => {    chrome.storage.local.set({      totalReplacementCount: (a.totalReplacementCount || 0) + 1    })  }), this.onMessage.emit({    type: s1,    message: {      sessionId: this.checkable.id,      rule: this.alert.rule,      context: this.alert.context,      replacement: t,      from: "alert",      sentence: this.alert.wholeSentence    }  })) : this.goPremiumTrialExpired()}

Background worker forwards the action to analytics

ext-background/main.js
this.onLogReplacement$ = ge(() => this.withOnlineCheckerAction(Gu).pipe($e(({  action: M,  checker: Y}) => {  Y.ensureIsConnected(), Y.logReplacement(M.rule, M.context, M.replacement, M.sentence), Cr.replacementClickedEvent(M.rule, M.context, M.replacement, M.from)})), {  dispatch: !1})

Analytics wrapper maps fields into ReplacementClicked

ext-background/main.js
class Cr {  static init(n) {    S0("050aec1607a54971d0794e2397b6979b", n)  }  static rephraseRequestedEvent(n, r, a, u) {    Cr.logEvent("RephraseRequested", {      type: n,      limitReached: r,      domain: a,      isSimplifier: u    })  }  static replacementClickedEvent(n, r, a, u) {    Cr.logEvent("ReplacementClicked", {      rule: n,      context: r,      replacement: a,      from: u    })  }  static logEvent(n, r) {    Yi(n, r)  }}

Report helper bounds context around the flagged text

ext-background/main.js
constructReport(n, r, a = 100) {  if (!this.currentData) return;  const p = this.currentData.text.split("\n")[n.lineNumber];  if (!p) return;  const C = p.substring(Math.max(0, n.offset - a), n.offset + n.length + a);  return {    id: ts.generateUuid(),    type: r,    context: C  }}

External analytics destination

    • api2.amplitude.com

    Amplitude HTTP API endpoint that receives the ReplacementClicked analytics event.

Where it sends data

Destinations our analysis observed Linguix: Grammar Checker and AI Writing App 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.

  • api2.amplitude.comwidely used

    Linguix: Grammar Checker and AI Writing App sends data to api2.amplitude.com. A widely used service: 42 other extensions we have analyzed send data here.

Updated 30 September 2026ndgklmlnheedegipcohgcbjhhgddendc