Is LI Prospect Finder safe?
LIPF is medium risk. When LI Prospect Finder can't parse certain LinkedIn search-result pages locally, it posts the page HTML to li-prospect-finder.com. The posted JSON's html field carries the full document, including rendering data used to extract records.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
LinkedIn search HTML sent to parsing endpoints
When LI Prospect Finder can't parse certain LinkedIn search-result pages locally, it posts the page HTML to li-prospect-finder.com.
The posted JSON's html field carries the full document, including rendering data used to extract records.
- Severity
- Medium unwanted
- Type
- Unexpected
- CWE
- CWE-359
- Source
- Dynamic sandbox
You run a LinkedIn company or people search through the extension.
The fallback paths appear when LinkedIn pages use the newer rendering data or when local people parsers return no results.
The extension posts the LinkedIn search-result HTML to its vendor server for parsing.
The request body is JSON with an html field containing the page document.
- LinkedIn page HTMLIllustrative HTML: <html><script>window.__como_rehydration__ = {"data":{"searchDashClustersByAll":{"metadata":{"primaryResultType":"COMPANIES"}}}}</script></html>
This can include the search-result markup and embedded LinkedIn rendering data for the profiles or companies shown on the page.
- Request field namehtml
The server receives the page document under a single html field, which is enough to reconstruct the search results being parsed.
- Accept
- application/json
- Ext-Version
- 3.2.12
- Content-Type
- application/json
- Accept
- application/json
- Ext-Version
- 3.2.12
- Content-Type
- application/json
Fallbacks that send LinkedIn search-result HTML
Company search fallback in readable form
deobfuscated/js/li/search/searchCompanies.jsasync function getCompaniesFromDefaultSearch() { let e = WEB_REQUEST_LI_API_URLS.COMPANIES_SEARCH; !0 === tab.url?.toLowerCase().includes("schools") && (e = WEB_REQUEST_LI_API_URLS.SCHOOLS_SEARCH); var a = await getLocalStorageValue(e); a && a[tab.id] && (searchUrl = a[tab.id], csrfToken) && (a = { "csrf-token": this.csrfToken, "x-restli-protocol-version": "2.0.0" }, 0 < searchUrl.indexOf(WEB_REQUEST_LI_API_URLS.NEW_API) && (a.accept = "application/vnd.linkedin.normalized+json+2.1"), $.ajaxSetup({ headers: a, global: !1, type: "GET" })); let t = !1, s = ""; tab.url?.toLowerCase().includes("companies") && (s = await $.get(tab.url) || "", t = s.includes("window.__como_rehydration__")), t ? companies = await apiCompaniesInfoFromHtml({ html: s }) || [] : (a = await $.get(searchUrl), (companies = liSearchCompaniesParser.handle(a)) && 0 !== companies.length || (companies = liSearchCompaniesParser.getCompanies(a))), renderCompaniesList()}People parser fallback in readable form
deobfuscated/js/li/search/searchProspectsParser.jsasync getPeopleListV4(e) { return { people: (await apiProspectsInfoFromHtml({ html: e }) || []).map(e => ({ ...e, source: "linkedIn", searchLink: `https://www.linkedin.com/voyager/api/identity/dash/profiles?q=memberIdentity&memberIdentity=${e.source_id_2}&decorationId=com.linkedin.voyager.dash.deco.identity.profile.FullProfileWithEntities-26` })), hasUnadded: !1 }}Automated company search pagination in readable form
deobfuscated/js/li/autoSearch/autoSearchCompanyTask.jsasync goToNextSearchPage() { let t = !1; if (!this.isFinished && !this.isPaused && this.isStarted) { var s; if (this.currentCompanies = [], (!0 === this.urlStart?.toLowerCase().includes("results/companies") || !0 === this.urlStart?.toLowerCase().includes("results/schools") || 0 < this.urlCurrent.indexOf(WEB_REQUEST_LI_API_URLS.NEW_API)) && (t = !0), +this.pageCurrent != +this.pageStart) return !(+this.pageCurrent <= +this.pageFinish) || (this.errorInternetCount = 0, this.urlCurrent = this.getCurrentSearchUrl(this.urlStart), 0 < this.urlCurrent.indexOf(WEB_REQUEST_LI_API_URLS.NEW_API) && $.ajaxSetup({ headers: { accept: "application/vnd.linkedin.normalized+json+2.1" }, global: !1, type: "GET" }), "string" == typeof(s = await $.get(this.urlCurrent)) && s.includes("window.__como_rehydration__") ? this.companies = await apiCompaniesInfoFromHtml({ html: s }) : this.companies = liSearchCompaniesParser.handle(s), this.companies && 0 !== this.companies.length || (this.list.length ? this.companies = this.list : this.companies = t ? liSearchCompaniesParser.getCompanies(s) : getCompaniesSN(s), this.checkIsPageAbsentError(s, t)), this.isPageAbsentError) ? void await this.finishPageHandler() : (await taskManager.setUnfinishedTask(getDeepCopyOfObject({ ...this, currentCompanies: [] })), void this.sendCompanyBlock()); this.list.length ? this.companies = this.list : this.companies = localStorage.companies ? JSON.parse(localStorage.companies) : [], this.needsSendCompaniesCount = this.companies.length + (this.pageFinish - this.pageStart) * this.companyCountOnPage, await this.sendCompanyBlock() }}Automated people search pagination in readable form
deobfuscated/js/li/autoSearch/autoSearchTask.jsasync goToNextSearchPage() { if (!this.isFinished && !this.isPaused && this.isStarted) if (+this.pageCurrent > +this.pageFinish) this.finish && this.finish(); else { this.errorInternetCount = 0, this.urlCurrent = this.getCurrentSearchUrl(this.urlStart); var t = this; if (this.list) i(); else { let e = this.urlCurrent; var s = { "csrf-token": this.csrfToken, "x-restli-protocol-version": "2.0.0" }, s = (0 < e.indexOf(WEB_REQUEST_LI_API_URLS.NEW_API) && (s.accept = "application/vnd.linkedin.normalized+json+2.1"), this.requestMethod === REQUEST_METHODS.POST && (e = this.urlCurrent.split("?")[0], s["Content-Type"] = "application/x-www-form-urlencoded", s["x-http-method-override"] = "GET"), { method: this.requestMethod, headers: s }); this.requestMethod === REQUEST_METHODS.POST && (s.body = this.urlCurrent.split("?")[1]); try { i(await (await fetch(e, s)).text()) } catch (e) { 0 === e.status ? (t.error = !0, t.errorCode = 0, t.errorOnPageGlobal()) : t.errorOnPage() } } } async function i(e) { !e && t.list ? (t.people = t.list, t.list = void 0) : (t.people = t.getPeopleList(e), t.people && t.people?.length || t.isSalesNavInt || (t.people = (await (new searchProspectsParser).getPeopleListV4(e))?.people)), t.getTrackingInfo && (t.trackingInfo = t.getTrackingInfo(e)), t.people && 0 < t.people.length ? (e = await t.checkPrevAddedPeople(t.people) || !1, t.needRecheckPrevAdded = e, await t.checkRecentlyUpdatedPeoples(t.people), t.itemsParsed = 0, t.waitAndGetPerson(), await reRenderTask(t)) : (t.itemsParsed = 0, t.waitAndGetPerson(), reRenderTask(t)), await reRenderTask(t) }}The API wrapper builds POST requests to the parsing endpoints
Endpoint constants and POST helper in readable form
deobfuscated/js/apiMethods.jslet API_HOST = APP_HOST + "/extension/api", USER_BALANCE = "/user/balance", PEOPLE_CREATE = "/peoples/create", PEOPLE_CONTACTS = "/peoples/contacts", PEOPLE_RECENTLY_UPDATED = "/peoples/recently-updated", COMPANIES_CREATE = "/companies/create", LISTS_BY_USER = "/lists/get-by-user-id", LISTS_BY_PROSPECTS = "/lists/get-by-peoples-ids", LISTS_BY_COMPANIES = "/lists/get-by-companies-ids", NEWS_GET = "/news/get-last", LI_HTML = "/linkedin/collect", COMPANIES_PARSE = "/companies/parse", PROSPECTS_PARSE = "/peoples/parse";function post(e, a) { return fetch(e, { method: "POST", body: JSON.stringify(a), headers: { "Content-Language": getUILanguage(), "Ext-Version": chrome.runtime.getManifest().version, "Content-Type": "application/json", Accept: "application/json" } })}Parsing endpoint wrappers in readable form
deobfuscated/js/apiMethods.jsasync function apiCompaniesInfoFromHtml(e) { e = await post(API_HOST + COMPANIES_PARSE, e); return e.ok && (await e.json())?.companies || []}async function apiProspectsInfoFromHtml(e) { e = await post(API_HOST + PROSPECTS_PARSE, e); return e.ok && (await e.json())?.peoples || []}- li-prospect-finder.com
LI Prospect Finder vendor host that receives LinkedIn search-result HTML at /extension/api/companies/parse and /extension/api/peoples/parse.