Is LeadContact safe?

Medium risk

LeadContact sends the URL of every page you visit to its own servers once you enable its 'browse everywhere' feature.

When a user opts into LeadContact's site-wide floating button, the extension registers a content script that runs on every website and sends the full URL of each page visited to api.leadcontact.ai, along with the user's session cookie, framed as a lookup for whether the button should be shown. Separately, on install and when that feature is enabled, the extension computes a persistent browser fingerprint (from canvas, WebGL, audio, fonts, and hardware signals, with a fallback fingerprint designed to survive anti-fingerprinting defenses) and sends it to LeadContact's tracking endpoint together with the session cookie.

linkedcrm2023v1.2.9Chrome Web Store
49Risk
Who publishes it

Company - no other listings under this identity, 9 shared hostnames

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

Publisher
linkedcrm2023
Declared legal entity
Company
Registered contact
WILLING TECH PTE. LTD.

Shared hosts - 9 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.

ad.letm
Also called by 2 other listings, including LeadContact-Phone Number & Email Finder for LinkedIn
ad2.traffi
Also called by 2 other listings, including LeadContact-Phone Number & Email Finder for LinkedIn
api.demo.leadcontact.ai
Also called by 2 other listings, including LeadContact-Phone Number & Email Finder for LinkedIn
api.leadcontact.ai
Also called by 2 other listings
app.leadcontact.ai
Also called by 2 other listings
app.read
Also called by 2 other listings
g1.v.fwm
Also called by 2 other listings, including LeadContact-Phone Number & Email Finder for LinkedIn
l.profi
Also called by 2 other listings
leadcontact.ai
Also called by 2 other listings

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

Publishers can request a review.

Findings

Opt-in 'browse everywhere' mode reports every page URL to LeadContact

HighunwantedCWE-200Static analysis

Code analysis shows that once you enable LeadContact's all-sites feature, a content script it installs on every website sends the page's full URL to LeadContact's own server on each page load, framed as a preference lookup.

What actually happens

You did this

You turn on LeadContact's all-sites feature and accept Chrome's all-sites permission prompt.

The extension did this

A script the extension runs on every site you visit reads the page's full address and sends it to LeadContact's server.

The script skips google.com, linkedin.com, leadcontact.ai and roughly ninety government, financial and policy-institute domains; every other site's address is sent.

The code that does this

Sitewide permission grant and content-script registration

Readable version

Enable-all-sites handler

src/background.js
async function enableAllSites() {  try {    if (!await chrome.permissions.request({ origins: ["*://*/*"] })) return false;    await chrome.storage.local.set({ AllSitesEnabled: true });    logEvent({ name: "ServiceEnableAllSites" });    const already = (await chrome.scripting.getRegisteredContentScripts())      .find(s => s.id === "sidebar");    if (!already) {      await chrome.scripting.registerContentScripts([{        id: "sidebar",        js: ["src/content-script/sidebar.js"],        matches: ["*://*/*"],        runAt: "document_idle",      }]);    }  } catch (e) { console.error("enableAllSites error:", e); return false; }}

Per-page URL capture and POST

src/content-script/sidebar.js
async function onPageLoad() {  if (isSkipListHost(["google", "leadcontact.ai", "linkedin.com"])      || await isForbiddenDomain(window.location.hostname)) return;  const currentUrl = window.top?.location.href || document.URL;  // Always: report the URL to check the account's login state  await checkLoginState(currentUrl); // POST /api/auth/check_login { targetUrl }  let preference = { floatBallVisible: true };  if (cache.get("logged")) {    // Logged in: also look up the per-page preference    const { code, data } = await getUserPreference({ targetUrl: currentUrl });    // POST https://api.leadcontact.ai/api/user/preference { targetUrl, ... }    if (code === SUCCESS) preference = data;  }  if (preference.floatBallVisible === false) return;  showFloatingButton();}

Fields in the request bodies sent to LeadContact's server

  • Page address
    https://www.example.com/account/billing?invoice=48213

    The full address of the page you're viewing, including its path and query string.

  • Account session
    connect.sid=s%3AaB12cD34eF56...

    Your LeadContact login session cookie, attached automatically to the request.

  • Extension ID and version
    cgfkgghhfpkdodlnkcbpabbghkkacdco / 1.2.9

    Identifies which build of the extension made the request.

  • Browser language
    en-US

    Your browser's interface language setting.

  • Browser and OS string
    Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36

    Your browser and operating system version.

Where the page addresses go

    • api.leadcontact.ai

    Receives the address of every site you visit, except a built-in skip list, to decide whether to show the extension's floating button.

Reproduce it yourself

Rebuilds the JSON body LeadContact's own code sends to api.leadcontact.ai from a page address, so you can compare it against what your browser's Network tab captures.

Requires
  • Node.js 18+ (or paste the function into any browser console)
leadcontact-request-reproducer.js · js
// Reproduces the request body built by content-script/sidebar.js's// ur()/f1()/kI() helpers for a given page URL.function buildRequestBody(pageUrl, opts = {}) {  return {    userAgent: opts.userAgent || (typeof navigator !== "undefined" ? navigator.userAgent : "<your UA>"),    locale: opts.locale || "en-US",    extId: opts.extId || "cgfkgghhfpkdodlnkcbpabbghkkacdco",    version: opts.version || "1.2.9",    targetUrl: pageUrl,  };}const body = buildRequestBody("https://www.example.com/account/billing?invoice=48213");console.log("POST https://api.leadcontact.ai/api/user/preference");console.log(JSON.stringify(body, null, 2));
How to run it
  1. 1Save and run node leadcontact-request-reproducer.js.
  2. 2Enable LeadContact's all-sites feature, open DevTools Network tab, filter leadcontact.ai, visit any site.
  3. 3Compare bodies.

Observation

Static analysis finding. This behaviour was identified by reading the shipped extension code and has not yet been reproduced in a live run. The trigger conditions and the exact data sent are read from the code, not from an observed capture.

Where it sends data

Destinations our analysis observed LeadContact 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.leadcontact.ai

    LeadContact sends data to api.leadcontact.ai. No other extension we have analyzed sends data here.

Updated 30 September 2026cgfkgghhfpkdodlnkcbpabbghkkacdco