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
45Risk

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

Publishers can request a review.

Findings

SeverityHIGH
ClassUNWANTED
TypeUnexpected
CWECWE-200
SourceAI FOUND

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

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.

01EvidenceCAUSE EFFECT
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.

02EvidenceCODE COMPARE
The code that does this

Sitewide permission grant and content-script registration

What it actually does
Enable-all-sites handlersrc/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 POSTsrc/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();
}
03EvidenceFIELD TABLE
Fields in the request bodies sent to LeadContact's server
FieldValueWhy it matters
Page address
https://www.example.com/account/billing?invoice=48213The 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.9Identifies which build of the extension made the request.
Browser language
en-USYour browser's interface language setting.
Browser and OS string
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36Your browser and operating system version.
04EvidenceTHIRD PARTY LIST
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.

05EvidenceARTIFACT
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.

RequiresNode.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. 1
    Save and run `node leadcontact-request-reproducer.js`.
  2. 2
    Enable LeadContact's all-sites feature, open DevTools Network tab, filter leadcontact.ai, visit any site.
  3. 3
    Compare bodies.
06EvidencePLAIN NOTE
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.

Data recipients

api.leadcontact.ai
Updated 20 September 2026cgfkgghhfpkdodlnkcbpabbghkkacdco