Is AeroLeads.com - Free B2B Phone Number & Email Finder safe?

High risk

Aeroleads.com - v6.3.9 is high risk. When a logged-in user saves a LinkedIn prospect in the AeroLeads panel, the extension reads profile fields and prepares a POST to AeroLeads: name, company, position, location, image URL, page URL. Dynamic capture failed at LinkedIn login.…

AeroLeadsv6.4.18Chrome Web Store
75Risk
Who publishes it

AeroLeads - no other listings under this identity

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

Publisher
AeroLeads
Registered address
AeroLeads, Koramangala, 3rd Block,, Bengaluru, KA 560034, India

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 SANDBOX

LinkedIn profiles saved through AeroLeads are posted to its API

When a logged-in user saves a LinkedIn prospect in the AeroLeads panel, the extension reads profile fields and prepares a POST to AeroLeads: name, company, position, location, image URL, page URL.

Dynamic capture failed at LinkedIn login.

01EvidenceCAUSE EFFECT
What actually happens
You did this

You save a LinkedIn prospect while signed in to LinkedIn and AeroLeads.

The behavior did not run in an unauthenticated test session because LinkedIn redirected to login and the extension showed an AeroLeads login form.

The extension did this

The extension reads the LinkedIn profile details and prepares them for the AeroLeads saved-prospect API.

The side panel collects the profile object into a save request after the content script sends it from the LinkedIn tab.

02EvidenceFIELD TABLE
Profile and page fields assembled for the prospect save request
FieldValueWhy it matters
LinkedIn profile name
Riley Chen (illustrative)Identifies the person you saved as a prospect.
Current company
Acme Robotics (illustrative)Links that person to an employer shown on the LinkedIn page.
Current position
Director of Partnerships (illustrative)Adds the role or headline shown on the profile.
Location
San Francisco Bay Area (illustrative)Adds the geographic location visible on the profile.
LinkedIn profile URL
https://www.linkedin.com/in/riley-chen/ (illustrative)Ties the saved record back to a specific LinkedIn page.
Profile image URL
https://media.licdn.com/dms/image/profile-displayphoto-shrink_200_200/0/1700000000000 (illustrative)Adds the profile photo URL when the page exposes one.
Tab context
LinkedIn profile tab title and URL (illustrative)Shows which browser tab produced the saved prospect record.
03EvidenceNETWORK CAPTURE
Captured request
POSThttps://aeroleads.com/api/prospects
No response was captured during dynamic analysis because the session lacked authenticated LinkedIn and AeroLeads access.
Headers
Content-Typeapplication/json
AuthorizationBearer token from the signed-in AeroLeads account
04EvidenceCODE COMPARE
The code that does this

LinkedIn profile extraction and AeroLeads prospect save path

What it actually does
Readable profile extractorcontent-runtime/index.iife.js
async extractProfileData() {
      var r, e, t, o, n;
      try {
        let i = {
          url: this.settings.tab.url,
          title: "",
          query: "",
          location: "",
          current_company: "",
          current_position: "",
          details_str: "",
          full_name: "",
          img_url: ""
        };
        const l = this.getRegularProfileContainer(),
          a = (l == null ? void 0 : l.querySelector(".mt2.relative")) || l || document;
        if (l || document.querySelector("main h1")) {
          const c = this.getExpectedProfileName(),
            d = c || this.getProfileNameFromContainer(a, c) || this.getText(a, ["h1", "h2", "main h1", "main h2", "h1.text-heading-xlarge"]);
          if (d && !this.isGenericLinkedInName(d)) i.title = d, i.query = d, i.full_name = d;
          else return !1;
          const m = this.getProfileSummaryLines(l || document, i.full_name || i.title || ""),
            u = this.getText(l || document, [".text-body-medium.break-words", ".pv-text-details__left-panel .text-body-medium", "div.text-body-medium"]) || m.find(p => !p.includes("·")) || "";
          if (u) {
            const {
              position: p,
              company: _
            } = this.companyPositionParser(u);
            i.current_position = p || u, i.current_company = _
          }
          const g = document.querySelector('button[aria-label^="Current company:"]') || document.querySelector('[aria-label*="Current company"]'),
            y = this.cleanCompanyText(((r = g == null ? void 0 : g.textContent) == null ? void 0 : r.trim()) || "");
          if (y && (i.current_company = y), i.current_company == "" || i.current_company == "N/A") {
            const p = m.find(_ => _.includes("·"));
            i.current_company = (p == null ? void 0 : p.split("·")[0].trim()) || i.current_company
          }
          if (s.log("BEFORE", i), i.current_company == "" || i.current_company == "N/A") {
            const p = document.querySelector('button[aria-label^="Current company:"]');
            if (p) {
              s.log("GET COMPANY", i);
              const _ = ((e = p.textContent) == null ? void 0 : e.trim()) || "";
              i.current_company = this.cleanCompanyText(_), s.log("Other currentCompanyElement", _)
            } else s.log("GET COMPANY NOT FOUND", i)
          }
          if (i.current_company == "" || i.current_company == "N/A") {
            const p = (t = document.querySelector("#experience")) == null ? void 0 : t.closest("section");
            if (p) {
              const _ = p.querySelector(".pv-entity__summary-info > h3") || (p == null ? void 0 : p.querySelector("ul > li:nth-of-type(1) > div > div:nth-of-type(2) > div > div > span:nth-of-type(1) > span")) || (p == null ? void 0 : p.querySelector("ul > li:nth-of-type(1) > div > div:nth-of-type(2) > div > a > span span"));
              if (_) {
                s.log("GET COMPANY 1", i);
                const S = ((o = _.textContent) == null ? void 0 : o.trim()) || "";
                i.current_company = this.cleanCompanyText(S)
              } else s.log("GET COMPANY 1 NOT FOUND", i)
            } else s.log("GET COMPANY 1 NOT FOUND", i)
          }
          if (i.current_company == "" || i.current_company == "N/A") {
            const p = document.querySelector('[aria-label*="Current company"]') || document.querySelector(".inline-show-more-text--is-collapsed");
            if (p) {
              s.log("GET COMPANY 2", i);
              const _ = ((n = p.textContent) == null ? void 0 : n.trim()) || "";
              i.current_company = this.cleanCompanyText(_), s.log("Other currentCompanyElement", _)
            } else s.log("GET COMPANY 2 NOT FOUND", i)
          }
          i.img_url = this.getImageSrc(l || document, i.full_name || i.title || "") || this.getImageSrc(document, i.full_name || i.title || ""), i.location = this.getText(l || document, [".text-body-small.inline.t-black--light", ".pv-text-details__left-panel .text-body-small", "span.text-body-small"]);
          let P = 0;
          for (; P < 3 && (i.current_company === "" || i.current_company === "N/A");) i = this.retryGetCompany(i), P++;
          return i.current_company !== "NA" && i.title !== "LinkedIn Member" ? this.pageProspect(i) : this.ncPageProspect(i), this.sendProfileData(i, "profile"), !0
        }
        return !1
      } catch (i) {
        return s.log("Error in extractProfileData:", i), !1
      }
    }
Readable profile message sendercontent-runtime/index.iife.js
sendProfileData(r, e) {
      try {
        chrome.runtime.sendMessage(chrome.runtime.id, {
          type: "SP::LINKEDIN_PROFILE_DATA",
          payload: {
            profile: r,
            pageType: e,
            tab: this.settings.tab
          }
        })
      } catch (t) {
        s.log("Error sending profile data:", t)
      }
    }
Readable API base and bearer-token wrapperside-panel/assets/index-C8nMy-Vk.js
const qy = "production",
  Ox = {
    development: "http://localhost:3000",
    staging: "https://aeroleadsmail.com",
    production: "https://aeroleads.com"
  },
  Qt = Ox[qy],
  ar = `${Qt}/api`,
const iB = (...e) => Z1(void 0, [...e], function*(t = {}) {
    const r = yield we.get(), n = Gp({}, t);
    return r && (n.headers = sB(Gp({}, n.headers), {
      Authorization: `Bearer ${r}`
    })), n
  }),
  wr = (e, t, r) => Z1(void 0, null, function*() {
    const n = (t == null ? void 0 : t.method) || "GET",
      a = (t == null ? void 0 : t.headers) || {
        "Content-Type": "application/json"
      },
      s = yield iB({
        method: n,
        url: e,
        headers: a
      });
    return t != null && t.data && (s.data = t == null ? void 0 : t.data), t != null && t.params && (s.params = t == null ? void 0 : t.params), yield(yield Ue(s)).data
  });
Readable prospects resource and autoCreate methodside-panel/assets/index-C8nMy-Vk.js
class UB extends K1 {
  constructor(t) {
    t || (t = {}), t.url = "prospects", super(t), this.search = r => a0(this, null, function*() {
      const n = {
        method: "POST",
        data: n0({}, r)
      };
      return yield wr(`${this.getURL(r)}/search`, n)
    }), this.autoCreate = r => a0(this, null, function*() {
      const n = {
        method: "POST",
        data: XB(n0({}, r), {
          auto: !0
        })
      };
      return yield wr(this.getURL(r), n)
    })
  }
}
const tn = new UB({});
Readable side-panel save dispatchside-panel/assets/index-C8nMy-Vk.js
B = g.useCallback(async () => {
      try {
        if (b || I) {
          ce.log("Already navigating or saving, skipping save");
          return
        }
        const z = s.filter(D => !y.get(Ke(D.url)));
        if (z.length > 0) {
          Z(z);
          const D = z.map(T => ({
            ...T,
            status: "adding"
          }));
          p(D);
          const L = new Set(z.map(T => Ke(T.url)));
          P(L), x(!0), h("Saving prospects..."), await U(oa({
            list_id: e,
            data: z
          })), ce.log("Save dispatch completed, waiting for status update...", {
            count: z.length
          })
        } else l && !v && !b && (E(!0), h("Loading new page..."), await R("next_page"))
      } catch (z) {
        ce.log("Error in saveCurrentPage:", z), E(!1), x(!1)
      }
    }, [l, v, s, y, e, b, I, U, Z, p, R])
05EvidenceTHIRD PARTY LIST
External service receiving the saved prospect request
  • aeroleads.com

    Receives saved LinkedIn prospect records through the extension API endpoint at /api/prospects.

SeverityMEDIUM
ClassUNWANTED
TypeUnexpected
CWECWE-200
SourceAI SANDBOX

LinkedIn profile URLs are sent to AeroLeads profile lookup API

Viewing a LinkedIn profile through the AeroLeads side panel sends that profile URL to AeroLeads' profile details API.

The URL can identify the person or company profile viewed and is used for a server-side enrichment lookup.

01EvidenceCAUSE EFFECT
What actually happens
You did this

The user views a LinkedIn profile while using the AeroLeads side panel.

The extension did this

The extension sends the LinkedIn profile URL to AeroLeads' profile details API for server-side lookup.

02EvidenceNETWORK CAPTURE
Captured request
GEThttps://aeroleads.com/api/v1/profiles/details
03EvidenceFIELD TABLE
Fields in the request
FieldValueWhy it matters
LinkedIn profile URL
https://www.linkedin.com/in/example-profile (illustrative)Identifies the LinkedIn profile being viewed and lets AeroLeads perform a profile lookup.
04EvidencePLAIN NOTE
Observation

Observed during dynamic analysis: the tested session was not logged into AeroLeads, so the profile lookup request was not triggered; the stored evidence shows the lookup runs when an authenticated user requests profile details from the side panel.

SeverityMEDIUM
ClassUNWANTED
TypeUnexpected
CWECWE-200
SourceAI SANDBOX

Bulk LinkedIn profile enrichment opens minimized windows to scrape profiles.

Bulk LinkedIn profile enrichment opens a minimized, unfocused window and loads each supplied profile URL, injecting a script to read page content: names, jobs, locations, images, company logos, URLs, and tab details.

01EvidenceCAUSE EFFECT
What actually happens
You did this

A logged-in user triggers bulk LinkedIn profile enrichment.

The extension did this

The extension opens a minimized, unfocused window, loads supplied LinkedIn profile URLs, and injects a script to read profile page fields.

02EvidenceFIELD TABLE
Fields read during profile enrichment
FieldValueWhy it matters
LinkedIn page content
Jane Smith, Example Corp, Sales Director, San FranciscoProfile page content can include a person's name, current company, role, location, profile image, and company logo.
LinkedIn profile URL
https://www.linkedin.com/in/jane-smith-123456/ (illustrative)The URL identifies which LinkedIn profile was loaded for enrichment.
Browser tab details
LinkedIn profile tab in minimized enrichment windowTab context shows which extension-created tab was used to load and process the profile page.
03EvidencePLAIN NOTE
Observation

Dynamic analysis confirmed the static code path from database evidence, while an earlier run could not trigger the behavior because the extension required AeroLeads authentication.

What it can do

Permissions this extension asks for, as declared in version 6.3.9. Asking for a permission is not a finding on its own - it is what the extension can do if it chooses to. The current listed version is 6.4.18, which we have not unpacked yet.

  • Read and change your data on aeroleads.com

    https://aeroleads.com/* and 1 more

  • Read and change your data on linkedin.com

    https://linkedin.com/* and 1 more

  • Read and change your data on google.com

    https://*.google.com/*

  • Store data in your browser

    storage

  • Run its own code inside the pages you visit

    scripting

  • See the address and title of every tab you have open

    tabs

  • Show a panel beside the page

    sidePanel

  • Show you desktop notifications

    notifications

  • Add items to the right-click menu

    contextMenus

Updated 30 September 2026fcpepipgmkkjnljechjjimkaondedmbe