Is WhenX: Mark Linkedin Profiles with notes safe?

High risk

WhenX sends note text, labels, tags, related URLs, and Google search queries to Mixpanel analytics.

When you create, edit, view, or expand notes, the extension forwards the note body, labels, tags, and associated page URLs through its offscreen Mixpanel analytics script. On Google search result pages, it also reads the current search query and sends it to the same Mixpanel endpoint.

WhenXv4.2.4Chrome Web Store
72Risk

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

Publishers can request a review.

Findings

Note text and profile URLs are sent to Mixpanel

When you create, edit, view, or expand a WhenX note, the extension sends the note body, label, tags, and page or profile URL to Mixpanel analytics at api.mixpanel.com, tagged as events like Note Created, Note Edited, and Note Viewed.

Severity
High unwanted
Type
Unexpected
CWE
CWE-359
Source
Dynamic sandbox
What actually happens
You did this

You create, edit, view, or expand a note in WhenX.

The note may be attached to a regular page, a search result, or a LinkedIn profile.

The extension did this

The extension routes the note text, label, tags, and associated URL into Mixpanel analytics.

The same analytics path covers created, edited, viewed, and expanded note events.

Note fields placed into Mixpanel event properties
  • Your note text
    Follow up after interview (illustrative)

    Text you write in the note can include private context about a person, company, or task.

  • Previous note text
    Intro call went well (illustrative)

    Edits include the earlier text as well as the replacement text, preserving what you changed.

  • Note label
    favorite

    Labels describe how you categorized the page or profile.

  • Note tags
    recruiting, follow-up (illustrative)

    Tags add more context about the person, account, or workflow connected to the note.

  • Associated page URL
    https://www.linkedin.com/in/jane-doe (illustrative)

    The URL ties the note activity to the exact page, search result, or profile where it was used.

Captured request
POSThttps://api.mixpanel.com/track/
Headers
Content-Type
application/x-www-form-urlencoded
The code that does this

The note analytics route from extension code to Mixpanel

Readable version

Readable background analytics wrapper and complete analytics object

scripts/background.js
    function Dd(e, t) {      (function() {        return Ad.apply(this, arguments)      })().then((function() {        chrome.runtime.sendMessage({          target: "offscreen-mixpanel",          method: e,          data: t        })      }))    }    var Od = {      init: function() {        Dd("init")      },      accountCreated: function(e, t, n, r, i) {        Dd("accountCreated", {          email: e,          name: t,          avatar: n,          fbuid: r,          profession: i        })      },      login: function(e, t, n, r, i, s, o) {        Dd("login", {          email: e,          name: t,          avatar: n,          fbuid: r,          profession: i,          subscriptionType: s,          notesCountLocal: o        })      },      setPlanType: function(e) {        Dd("setPlanType", {          subscriptionType: e        })      },      redirectedToWebLogin: function() {        Dd("redirectedToWebLogin")      },      webLoginTokenReturned: function(e) {        Dd("webLoginTokenReturned", {          userIsNew: e        })      },      planUpgraded: function(e) {        Dd("planUpgraded", {          subscriptionType: e        })      },      planDowngraded: function(e) {        Dd("planDowngraded", {          subscriptionType: e        })      },      noteBoxFired: function(e, t) {        Dd("noteBoxFired", {          method: e,          url: t        })      },      popupOpened: function(e) {        Dd("popupOpened", {          url: e        })      },      noteCreated: function(e, t, n, r, i, s) {        Dd("noteCreated", {          noteBody: e,          noteSource: t,          url: n,          label: r,          tags: i,          notesCount: s        })      },      noteEdited: function(e, t, n, r, i, s, o, a) {        Dd("noteEdited", {          body: e,          oldBody: t,          label: n,          oldLabel: r,          tags: i,          oldTags: s,          url: o,          editedFromDashboard: a        })      },      noteViewed: function(e, t, n) {        Dd("noteViewed", {          noteBody: e,          url: t,          label: n        })      },      profileNoteViewed: function(e, t, n, r) {        Dd("profileNoteViewed", {          noteBody: e,          url: t,          label: n,          tags: r        })      },      noteExpanded: function(e, t, n) {        Dd("noteExpanded", {          noteBody: e,          url: t,          label: n        })      },      profileNoteExpanded: function(e, t, n) {        Dd("profileNoteExpanded", {          noteBody: e,          url: t,          label: n        })      },      noteBadgeDisplayed: function(e) {        Dd("noteBadgeDisplayed", {          label: e        })      },      noteDeleted: function(e) {        Dd("noteDeleted", {          url: e        })      },      noteCreationFailed: function(e, t, n, r) {        Dd("noteCreationFailed", {          noteBody: e,          url: t,          errorMsg: n,          version: r        })      },      noteEditFailed: function(e, t, n, r, i) {        Dd("noteEditFailed", {          noteBody: e,          url: t,          errorMsg: n,          version: r,          isDashboardNote: i        })      },      timestampViewed: function() {        Dd("timestampViewed")      },      timestampHovered: function() {        Dd("timestampHovered")      },      profileTimestampViewed: function(e) {        Dd("profileTimestampViewed", {          url: e        })      },      profileTimestampHovered: function(e) {        Dd("profileTimestampHovered", {          url: e        })      },      searchQuery: function(e) {        Dd("searchQuery", {          query: e        })      },      dashboardItemUrlClicked: function(e) {        Dd("dashboardItemUrlClicked", {          url: e        })      },      track: function(e) {        Dd("trackEvent", {          eventName: e        })      },      trackEventWithProperties: function(e, t) {        Dd("trackEventWithProperties", {          eventName: e,          props: t        })      },      extensionInstalled: function(e) {        Dd("extensionInstalled", {          version: e        })      },      extensionUpdated: function(e) {        Dd("extensionUpdated", {          version: e        })      },      onboardingContinued: function(e) {        Dd("onboardingContinued", {          profession: e        })      },      onboardingDismissed: function() {        Dd("onboardingDismissed")      }    };    const Rd = Od;

Readable offscreen Mixpanel worker

scripts/offscreen-mixpanel.js
    s().init("3837db1d7db2b8c12bcd394e379bf8c2", {      api_host: "https://api.mixpanel.com"    }), setInterval(t(i().mark((function e() {      return i().wrap((function(e) {        for (;;) switch (e.prev = e.next) {          case 0:            return e.next = 2, navigator.serviceWorker.ready;          case 2:            e.sent.active.postMessage("keepAlive");          case 3:          case "end":            return e.stop()        }      }), e)    }))), 2e4), chrome.runtime.onMessage.addListener((function(e, t, r) {      if ("offscreen-mixpanel" !== e.target) return !1;      var o, i, n, a, c, p, u, _, l, h, d, f, g = e.method,        y = e.data;      switch (g) {        case "accountCreated":          _ = y.email, l = y.name, h = y.avatar, d = y.fbuid, f = y.profession, s().alias(_), s().track("Sign Up"), s().people.set({            $email: _,            $name: l,            $avatar: h,            "Firebase uid": d,            Profession: f          });          break;        case "login":          ! function(e, t, r, o, i, n, a, c) {            s().identify(e), s().register({              "Plan Type": n,              "Notes Created Count": a,              "Likes Created Count": c            }), s().people.set_once({              $email: e,              $name: t,              $avatar: r,              "Firebase uid": o,              Profession: i,              "Notes Created Count": a,              "Likes Created Count": c            }), s().people.set({              "Plan Type": n            }), s().track("Login")          }(y.email, y.name, y.avatar, y.fbuid, y.profession, y.subscriptionType, y.notesCountLocal, y.ratingsCountLocal);          break;        case "setPlanType":          u = y.subscriptionType, s().people.set({            "Plan Type": u          });          break;        case "redirectedToWebLogin":          s().track("Redirected To Web Login");          break;        case "webLoginTokenReturned":          p = y.userIsNew, s().track("Web Login Token Returned", {            "User Is New": p          });          break;        case "planUpgraded":          ! function(e) {            s().register({              "Plan Type": e            }), s().track("Plan Upgraded"), s().people.set({              "Plan Type": e            })          }(y.subscriptionType);          break;        case "planDowngraded":          ! function(e) {            s().register({              "Plan Type": e            }), s().track("Plan Downgraded"), s().people.set({              "Plan Type": e            })          }(y.subscriptionType);          break;        case "noteBoxFired":          ! function(e, t) {            s().track("Note Box Opened", {              "Note Creation Method": e,              "Note Url": t            }), s().people.set({              "Last Note Box Fired": (new Date).toISOString()            })          }(y.method, y.url);          break;        case "popupOpened":          c = y.url, s().track("Popup Opened", {            "Page Url": c          });          break;        case "noteCreated":          ! function(e, t, r, o, i, n) {            s().track("Note Created", {              "Note Creation Source": t,              "Note Label": o,              "Note Tags": i,              "Note Body": e,              "Note Url": r            }), n > 2 && s().people.set_once({              "First Note Created": (new Date).toISOString()            });            s().people.set({              "Last Note Created": (new Date).toISOString()            }), s().people.increment("Notes Created Count")          }(y.noteBody, y.noteSource, y.url, y.label, y.tags, y.notesCount);          break;        case "noteEdited":          ! function(e, t, r, o, i, n, a, c) {            s().track("Note Edited", {              "Note Body": e,              "Old Note Body": t,              "Note Label": r,              "Old Note Label": o,              "Note Tags": i,              "Old Note Tags": n,              "Note Url": a,              "Edited From Dashboard": c            }), s().people.set({              "Last Note Edited": (new Date).toISOString()            }), s().people.increment("Notes Edited Count")          }(y.body, y.oldBody, y.label, y.oldLabel, y.tags, y.oldTags, y.url, y.editedFromDashboard);          break;        case "noteViewed":          ! function(e, t, r) {            s().track("Note Viewed", {              "Note Body": e,              "Note Url": t,              "Note Label": r            }), s().people.set({              "Last Note Viewed": (new Date).toISOString()            }), s().people.increment("Notes Viewed Count")          }(y.noteBody, y.url, y.label);          break;        case "profileNoteViewed":          ! function(e, t, r, o) {            s().track("Profile Note Viewed", {              "Note Body": e,              "Note Url": t,              "Note Label": r,              "Note Tags": o            }), s().people.set({              "Last Profile Note Viewed": (new Date).toISOString()            }), s().people.increment("Profile Notes Viewed Count")          }(y.noteBody, y.url, y.label, y.tags);          break;        case "noteExpanded":          ! function(e, t, r) {            s().track("Note Expanded", {              "Note Body": e,              "Note Url": t,              "Note Label": r            }), s().people.increment("Notes Expanded Count")          }(y.noteBody, y.url, y.label);          break;        case "profileNoteExpanded":          ! function(e, t, r) {            s().track("Profile Note Expanded", {              "Note Body": e,              "Note Url": t,              "Note Label": r            }), s().people.increment("Profile Notes Expanded Count")          }(y.noteBody, y.url, y.label);          break;        case "noteBadgeDisplayed":          a = y.label, s().track("Note Badge Displayed", {            "Note Label": a          }), s().people.increment("Note Badges Displayed Count");          break;        case "noteDeleted":          ! function(e) {            s().track("Note Deleted", {              "Note Url": e            })          }(y.url);          break;        case "noteCreationFailed":          ! function(e, t, r, o) {            s().track("Note Creation Failed", {              "Note Body": e,              "Note Url": t,              "Error Message": r,              "Extension Version": o            })          }(y.noteBody, y.url, y.errorMsg, y.version);          break;        case "noteEditFailed":          ! function(e, t, r, o, i) {            s().track("Note Editing Failed", {              "Note Body": e,              "Note Url": t,              "Error Message": r,              "Extension Version": o,              "Edited From Dashboard": i            })          }(y.noteBody, y.url, y.errorMsg, y.version, y.isDashboardNote);          break;        case "timestampViewed":          s().track("Timestamp Viewed"), s().people.set({            "Last Timestamp Viewed": (new Date).toISOString()          }), s().people.increment("Timestamps Viewed Count");          break;        case "timestampHovered":          s().track("Timestamp Hovered"), s().people.increment("Timestamps Hovered Count");          break;        case "profileTimestampViewed":          ! function(e) {            s().track("Profile Timestamp Viewed", {              "Timestamp Url": e            }), s().people.set({              "Last Profile Timestamp Viewed": (new Date).toISOString()            }), s().people.increment("Profile Timestamps Viewed Count")          }(y.url);          break;        case "profileTimestampHovered":          ! function(e) {            s().track("Profile Timestamp Hovered", {              "Timestamp Url": e            }), s().people.increment("Timestamps Hovered Count")          }(y.url);          break;        case "likeViewed":          ! function(e) {            s().track("Like Viewed", {              "Like Url": e            }), s().people.set({              "Last Like Viewed": (new Date).toISOString()            }), s().people.increment("Likes Viewed Count")          }(y.url);          break;        case "likeCreated":          ! function(e) {            s().track("Like Created", {              "Like Url": e            }), s().people.set({              "Last Like Created": (new Date).toISOString()            }), s().people.increment("Likes Created Count")          }(y.url);          break;        case "likeDeleted":          ! function(e) {            s().track("Like Deleted", {              "Like Url": e            })          }(y.url);          break;        case "likeCreationFailed":          ! function(e, t, r) {            s().track("Like Creation Failed", {              "Like Url": e,              "Error Message": t,              "Extension Version": r            })          }(y.url, y.errorMsg, y.version);          break;        case "searchQuery":          n = y.query, s().track("Search Query", {            "Search Query": n          }), s().people.set({            "Last Search": (new Date).toISOString()          }), s().people.increment("Searches Count");          break;        case "dashboardItemUrlClicked":          ! function(e) {            s().track("Dashboard Item Url Clicked", {              "Dashboard Item Url": e            })          }(y.url);          break;        case "trackEvent":          i = y.eventName, s().track(i);          break;        case "trackEventWithProperties":          ! function(e, t) {            s().track(e, t)          }(y.eventName, y.props);          break;        case "extensionInstalled":          o = y.version, s().track("Install", {            "Extension Version": o          }), s().people.set({            "Extension Version": o          }), s().register({            "Extension Version": o          });          break;        case "extensionUpdated":          ! function(e) {            s().track("Extension Updated", {              Version: e            }), s().people.set({              "Extension Version": e            }), s().register({              "Extension Version": e            })          }(y.version);          break;        case "onboardingContinued":          ! function(e) {            s().people.set({              Profession: e            }), s().register({              Profession: e            }), s().track("Onboarding Continue Btn Clicked")          }(y.profession);          break;        case "onboardingDismissed":          s().track("Onboarding Dismissed")      }      return "trackEvent" === e.type && function(e) {        var t = e.eventName,          r = e.properties;        r ? s().track(t, r) : s().track(t)      }(e), r({        success: !0      }), !0    }))  })()})();

Readable LinkedIn note UI messages that provide note fields

scripts/createLinkedinNote.js
          key: "updateNote",          value: (t = r(o().mark((function e() {            var t;            return o().wrap((function(e) {              for (;;) switch (e.prev = e.next) {                case 0:                  return t = this, e.abrupt("return", new Promise((function(e, n) {                    chrome.runtime.sendMessage({                      method: "update-note",                      noteId: t.id,                      noteBody: t.body,                      oldNoteBody: t.oldBody,                      linkUrl: t.url,                      candidateName: t.candidateName,                      candidatePic: t.candidatePic,                      candidatePosition: t.candidatePosition,                      label: t.label,                      tags: t.tags,                      oldLabel: t.oldLabel,                      oldTags: t.oldTags,                      isDashboardNote: !1                    }, (function(t) {                      t && t.status && !t.farewell ? e(!0) : e(!1)                    }))                  })));                case 2:                case "end":                  return e.stop()              }            }), e, this)          }))), function() {            return t.apply(this, arguments)          })        }, {          key: "addNote",          value: (e = r(o().mark((function e() {            var t;            return o().wrap((function(e) {              for (;;) switch (e.prev = e.next) {                case 0:                  return t = this, e.abrupt("return", new Promise((function(e, n) {                    chrome.runtime.sendMessage({                      method: "add-note",                      noteBody: t.body,                      linkUrl: t.url,                      candidateName: t.candidateName,                      candidatePic: t.candidatePic,                      candidatePosition: t.candidatePosition,                      label: t.label,                      tags: t.tags,                      src: t.noteSource                    }, (function(t) {                      t && t.status && !t.farewell ? e(!0) : e(!1)                    }))                  })));                case 3:                case "end":                  return e.stop()              }
External analytics destination
    • api.mixpanel.com

    Mixpanel analytics endpoint receiving note event properties, including note text fields and associated URLs.

Where it sends data

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

  • api.mixpanel.comwidely used

    WhenX sends data to api.mixpanel.com. A widely used service: 35 other extensions we have analysed send data here.

Updated 30 September 2026dgafcidlgmbcehokgdeghmfnbpbfhihh