Is Cognism - Premium Sales Intelligence safe?
Cognism is high risk. On LinkedIn, Sales Navigator, Recruiter, Outreach, Salesforce, and HubSpot pages, Cognism's content script can return page HTML plus the URL on request. DA couldn't reach this: Cognism returned 401, LinkedIn redirected to sign-in.…
Who publishes itCOGNISM Limited - 1 other listing from the same operator, 1 of them carrying a finding
COGNISM Limited - 1 other listing from the same operator, 1 of them carrying a finding
What this publisher told the store about itself, and the other listings that told it the same thing.
Same store account
1 other listing published from this account, 2k+ users between them. 1 of them carries a finding.
Shared hosts - 2 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.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Full page HTML collection on sales and CRM pages
On LinkedIn, Sales Navigator, Recruiter, Outreach, Salesforce, and HubSpot pages, Cognism's content script can return page HTML plus the URL on request.
DA couldn't reach this: Cognism returned 401, LinkedIn redirected to sign-in.
- Severity
- High unwanted
- Type
- Unexpected
- CWE
- CWE-200
- Source
- Dynamic sandbox
You use the Cognism workflow on a supported sales, recruiting, or CRM page.
The supported URL patterns include LinkedIn profiles, Sales Navigator, Recruiter, Outreach, Salesforce, and HubSpot pages.
The content script can collect the page markup and return it to the extension.
The source reads the document's HTML tag, head markup, and body markup before sending them back through the extension message callback.
- Document HTML marker<html></html>
This identifies the page document that was open when the extension handled the request.
- Page head HTML<head><title>LinkedIn Profile</title></head>
This can include page metadata, loaded scripts, embedded attributes, and other non-visible page details.
- Page body HTML<body><main><section>William Gates - Co-chair, Bill & Melinda Gates Foundation</section></main></body>
This can include profile details, CRM records, visible text, form values present in markup, and identifiers rendered on the page.
- Page URL contexthttps://www.linkedin.com/in/williamhgates
This ties the collected markup to the specific supported record, profile, company, or search page you were viewing.
401 Unauthorized during dynamic analysis; the DOM collection path was not reached in that session.
The shipped content script reads full page markup and returns it over extension messaging
The popup-side helper requests body HTML from the content script
common.jsgetPageOuterHtml$(x) { const m = { id: (0, e.Ak)(), type: h.p.DOM, action: c.GET_BODY_OUTER_HTML, data: { url: x } }; return this.sendMessage$(m).pipe((0, E.T)(R => R.data), (0, y.M)(R => this.loggerService.log(["ContentDomService connection message", R])), (0, d.j)(() => { m.action = c.CANCEL_REQUEST, this.sendMessage$(m).subscribe() }))}The content script reads the document head and body HTML
content.jsgetDomPage(o) { const M = { html: `${(/<html.*?>/i.exec(document.documentElement.outerHTML)||[])[0]||"<html>"}</html>`, head: document.head.outerHTML, body: document.body.outerHTML }; return o && (M.version = o), M}The DOM message listener returns the collected markup
content.jssetListeners() { chrome.runtime.onMessage.addListener((o, a, c) => { this.loggerService.log(["DomService message", o]); const P = o?.type, I = o?.action, M = o?.id, Q = o?.data?.url; if (P !== Nn.DOM) return !1; if (I === ya.CANCEL_REQUEST) { const te = this.subscriptions.get(M); return te && (te.unsubscribe(), this.subscriptions.delete(M)), !1 } const Z = this.getPageHtml$(Q).subscribe({ next: te => { this.subscriptions.delete(M), c({ type: Nn.DOM, action: ya.GET_BODY_OUTER_HTML, data: te }) }, error: te => { this.subscriptions.delete(M), c(te) } }); return this.subscriptions.set(M, Z), !0 })}LinkedIn session-backed API requests from the content script
The extension can use the active LinkedIn session to call Sales Navigator/Recruiter endpoints, reading JSESSIONID for the CSRF header, setting withCredentials, and building profile/company/search requests.
DA saw none: session lacked auth.
- Severity
- High unwanted
- Type
- Unexpected
- CWE
- CWE-200
- Source
- Dynamic sandbox
You use Cognism on a supported LinkedIn Sales Navigator or Recruiter page.
The relevant code paths are tied to LinkedIn profile, company, people-search, lead-search, and Recruiter profile workflows.
The content script can send LinkedIn API requests using your active LinkedIn browser session.
The request configuration includes a CSRF token read from cookies and credentials enabled so the browser session is used.
- LinkedIn CSRF valueajax:8392746501928374650
This lets the request use the active LinkedIn web session from your browser.
- LinkedIn session cookiesli_at=redacted-session-cookie; JSESSIONID="ajax:8392746501928374650"
These tie the API request to the LinkedIn account currently signed in on the page.
- Profile detailsWilliam Gates, Co-chair, Bill & Melinda Gates Foundation, Seattle, Washington
The requested decorations include names, headlines, profile URLs, summaries, contact information, positions, and related profile data.
- LinkedIn page URLhttps://www.linkedin.com/sales/lead/ACwAAAEAAAA,NAME_SEARCH,abc123
The code parses the current LinkedIn URL to decide which profile, company, list, or search request to make.
Request shape from shipped source code; dynamic analysis recorded zero sales-api or talent-api requests because authentication gates blocked the workflow.
- csrf-token
- ajax:8392746501928374650
- x-restli-protocol-version
- 2.0.0
Request shape from shipped source code; dynamic analysis recorded zero sales-api or talent-api requests because authentication gates blocked the workflow.
- csrf-token
- ajax:8392746501928374650
- x-http-method-override
- GET
- x-restli-protocol-version
- 2.0.0
The shipped content script builds LinkedIn API requests with the browser session
CSRF token comes from the LinkedIn JSESSIONID cookie
content.jsconst ns = () => { const o = (document.cookie.match(/JSESSIONID=(.*?)(?:;|$)/) || [])[1] || ""; return (0, F.replace)(o, /"/g, "")};Recruiter profile request uses csrf-token and withCredentials
content.jsgetRecruiterProfile$(o) { const a = this.getProfileApiUrl(o), c = { headers: { "csrf-token": ns(), "x-restli-protocol-version": "2.0.0", "x-http-method-override": "GET" }, withCredentials: !0 }, P = `altkey=urn&${this.profileParamDecoration}`; return Nr().post(a, P, c).pipe(re(I => I?.data || null))}getProfileApiUrl(o) { const a = (0, Ri.parseUrl)(o); return `https://www.linkedin.com/talent/api/talentLinkedInMemberProfiles/${`urn:li:ts_linkedin_member_profile:(${(0,F.first)((0,F.split)((0,F.last)((0,F.split)(a.url,"profile/")),"/"))},1,urn:li:ts_hiring_project:(urn:li:ts_contract:${this.urnLiContract},${a.query.project}))`.replace(/:\s*/g,ki.COLON).replace(/,\s*/g,ki.COMMA)}`}Sales Navigator profile and company requests use csrf-token and credentials
content.jsgetSalesNavigatorProfile$(o) { let a = "https://www.linkedin.com/sales-api/salesApiProfiles"; const P = (0, Ri.parseUrl)(o).url.split(","), I = P[0].split("/").pop(), M = P[1], Q = P[2]; a += `/(profileId:${I}${M?`,authType:${M}`:""}${Q?`,authToken:${Q}`:""})`; const te = { headers: { "csrf-token": ns(), "x-restli-protocol-version": "2.0.0" }, params: { decoration: this.formatQuery("(entityUrn,objectUrn,firstName,lastName,fullName,headline,memberBadges,latestTouchPointActivity,pronoun,degree,profileUnlockInfo,location,listCount,summary,savedLead,defaultPosition,contactInfo,crmStatus,pendingInvitation,unlocked,flagshipProfileUrl,fullNamePronunciationAudio,memorialized,numOfConnections,numOfSharedConnections,showTotalConnectionsPage,positions*(companyName,current,new,description,endedOn,posId,startedOn,title,location,richMedia*,companyUrn~fs_salesCompany(entityUrn,name,companyPictureDisplayImage)),crmManualMatched)") }, paramsSerializer: pe => { let we = `decoration=${encodeURIComponent(pe.decoration)}`; return we = we.replace(/\(/g, "%28").replace(/\)/g, "%29"), `${we}` }, withCredentials: !0 }; return Nr().get(a, te).pipe(re(pe => pe?.data || null))}getSalesNavigatorCompany$(o) { const I = `https://www.linkedin.com/sales-api/salesApiCompanies/${((0,Ri.parseUrl)(o).url.match(/.*linkedin.com\/sales\/company\/(.*?)(?:\/|$)/)||[])[1]||""}`, M = { headers: { "csrf-token": ns(), "x-restli-protocol-version": "2.0.0" }, params: { decoration: "(entityUrn,name,account(saved,noteCount,listCount,crmStatus,starred),pictureInfo,companyPictureDisplayImage,description,industry,location,headquarters,website,revenueRange,crmOpportunities,flagshipCompanyUrl,employeeGrowthPercentages,employees*~fs_salesProfile(entityUrn,firstName,lastName,fullName,pictureInfo,profilePictureDisplayImage),specialties,type,yearFounded)" }, paramsSerializer: Q => { let Z = `decoration=${encodeURIComponent(Q.decoration)}`; return Z = Z.replace(/\(/g, "%28").replace(/\)/g, "%29"), Z }, withCredentials: !0 }; return Nr().get(I, M).pipe(re(Q => Q?.data))}Sales Navigator search can call people-search or lead-search endpoints
content.jsgetSalesNavigatorProfileSearch$(o, a) { const c = (0, F.replace)(o, "#", "?"); let P = (0, Ri.parseUrl)(c); const I = this.isNewApi(P.query), M = "https://www.linkedin.com/sales-api/" + (I ? "salesApiLeadSearch" : "salesApiPeopleSearch"), Q = (0, F.assign)({ start: 0, count: 25 }, a?.pagination), Z = { headers: { "csrf-token": ns(), "x-restli-protocol-version": "2.0.0" }, withCredentials: !0 }; let te = { start: Q.start, count: Q.count }, pe = null; if (I) { const we = P.query?.savedSearchId && "savedSearchId", He = P.query?.recentSearchId && "recentSearchId", Me = P.query?.sharedSearchId && "sharedSearchId", $e = P.query?.query; te.q = we || He || Me || "searchQuery", te.query = (0, F.get)($e?.match(/\((.+)\)/), "[1]", null), te.decorationId = "com.linkedin.sales.deco.desktop.searchv2.LeadSearchResult-7", te.companyIncluded = P.query?.companyIncluded, te.sessionId = P.query?.sessionId, te.recentSearchId = P.query?.recentSearchId, te.savedSearchId = P.query?.savedSearchId; let Ze = (0, F.split)(P.query?.sharedSearchId, ","); Ze = (0, F.filter)(Ze, We => !isNaN(parseInt(We))), 2 === Ze.length ? (te.sharerSeatId = Ze[0], te.sharedSearchId = Ze[1]) : te.sharedSearchId = P.query?.sharedSearchId, pe = We => { const nt = `q=${We.q}`, Xe = `start=${We.start}`, mt = `count=${We.count}`, gn = We.companyIncluded ? `filters:List((type:CURRENT_COMPANY,values:List(${this.prepareList(We.companyIncluded)})))` : "", En = We.decorationId ? `&decorationId=${We.decorationId}` : "", Ar = We.sessionId ? `&trackingParam=(sessionId:${We.sessionId})` : "", Zi = We.recentSearchId ? `&recentSearchId=${We.recentSearchId}` : "", Jn = We.savedSearchId ? `&savedSearchId=${We.savedSearchId}` : "", qr = We.sharerSeatId ? `&sharerSeatId=${We.sharerSeatId}` : "", Wi = We.sharedSearchId ? `&sharedSearchId=${We.sharedSearchId}` : "", qi = (0, F.split)(We.query, ","), ir = (0, F.map)(qi, eo => (0, F.startsWith)(eo, "keywords:") ? `${this.encodeCharacters(eo)}` : eo).join(","); return `${nt}&${Xe}&${mt}${(ir?`&query=(${ir})`:"")||(gn?`&query=(${gn})`:"")}${En}${Ar}${Zi}${Jn}${qr}${Wi}` } } else { P = (0, Ri.parseUrl)(o); const we = this.getSearchType(P.query), He = this.getSearchParams(o, P.query, we); te = { ...te, ...He }, pe = this.getSearchParamsSerializer(we) } if ((0, F.includes)(o, "#")) { Z.headers["content-type"] = "application/x-www-form-urlencoded", Z.headers["x-http-method-override"] = "GET"; const we = pe(te); return Nr().post(M, we, Z).pipe(re(He => He?.data)) } return Z.params = te, Z.paramsSerializer = pe, Nr().get(M, Z).pipe(re(we => we?.data))}- www.linkedin.com
Receives Sales Navigator and Recruiter API requests built by the content script using the active LinkedIn browser session.
- app.cognism.com
Authentication check observed during dynamic analysis; it returned 401 and prevented the tested workflow from reaching LinkedIn API calls.
LinkedIn profile HTML is posted to Cognism for matching
On a LinkedIn profile, Cognism can collect the profile URL and, if HTML collection is on, the page body HTML.
Shipped code preps this for Cognism's matching service and posts to Cognism hosts.
DA captured auth traffic, not the POST body.
- Severity
- Medium unwanted
- Type
- Unexpected
- CWE
- CWE-200
- Source
- Dynamic sandbox
You view a LinkedIn profile while using the Cognism extension.
The extension prepares your LinkedIn profile context for Cognism profile matching.
If HTML collection and advanced matching are enabled, the request also includes a compressed copy of the page body.
- LinkedIn profile URLhttps://www.linkedin.com/in/jane-doe/ (illustrative)
This identifies the profile you are viewing and lets Cognism match it to a person record.
- LinkedIn page body<body><main><section>Jane Doe - Account Executive at ExampleCo</section></main></body> (illustrative)
This can include visible profile text and page structure from the LinkedIn page you opened.
- Extension versionCE:4.0.26
This tells Cognism which extension build prepared the request.
- Source labelLinkedIn
This marks the request as coming from a LinkedIn profile workflow.
The shipped functions that gather page HTML and post it to Cognism
LinkedIn profile route gathers HTML and calls profile matching
deobfuscated/928.jsget isHtmlGrabEnabled() { return this.storageService.storage.options.isHtmlGrabEnabled}setProfileAndCompany$(s) { return (0, a.of)(null).pipe((0, p.Z)(() => { this.getProfileInProgress = !0, this.cdr.markForCheck(); const t = this.isHtmlGrabEnabled ? this.contentDomService.getPageOuterHtml$(s) : (0, a.of)({ html: null, head: null, body: null }); return (0, C.p)([(0, a.of)(s), t]) }), (0, p.Z)(([t, i]) => this.getProfile$(t, i).pipe((0, p.Z)(r => { let l = (0, a.of)(r?.company); return (r?.optOut || r?.restricted) && (l = this.getCompany$(r?.company?.id)), (0, C.p)([(0, a.of)(r), l]) }))), (0, P.T)(([t, i]) => (this.profile = t, this.profile && (this.profile.company = i), t)), (0, P.T)(() => null), (0, k.j)(() => { this.getProfileInProgress = !1, this.cdr.markForCheck() }))}getProfile$(s, t) { return this.cognismProfileService.getProfileByHtml$(s, t).pipe((0, p.Z)(i => this.cognismProfileService.tryEnrichPhoneNumbers$(i)), (0, y.W)(i => (console.log("LP_P", i), (0, a.of)(null))))}Content DOM request sent from the extension page
deobfuscated/common.jsgetPageOuterHtml$(x) { const m = { id: (0, e.Ak)(), type: h.p.DOM, action: c.GET_BODY_OUTER_HTML, data: { url: x } }; return this.sendMessage$(m).pipe((0, E.T)(R => R.data), (0, y.M)(R => this.loggerService.log(["ContentDomService connection message", R])), (0, d.j)(() => { m.action = c.CANCEL_REQUEST, this.sendMessage$(m).subscribe() }))}Content script returns page HTML
deobfuscated/content.jsgetDomPage(o) { const M = { html: `${(/<html.*?>/i.exec(document.documentElement.outerHTML)||[])[0]||"<html>"}</html>`, head: document.head.outerHTML, body: document.body.outerHTML }; return o && (M.version = o), M}setListeners() { chrome.runtime.onMessage.addListener((o, a, c) => { this.loggerService.log(["DomService message", o]); const P = o?.type, I = o?.action, M = o?.id, Q = o?.data?.url; if (P !== Nn.DOM) return !1; if (I === ya.CANCEL_REQUEST) { const te = this.subscriptions.get(M); return te && (te.unsubscribe(), this.subscriptions.delete(M)), !1 } const Z = this.getPageHtml$(Q).subscribe({ next: te => { this.subscriptions.delete(M), c({ type: Nn.DOM, action: ya.GET_BODY_OUTER_HTML, data: te }) }, error: te => { this.subscriptions.delete(M), c(te) } }); return this.subscriptions.set(M, Z), !0 })}Profile request body and POST destination
deobfuscated/878.jsgetProfileByHtml$(A, f) { const v = `${this.apiUrl}/graph/person/view`, z = this.utilityService.prepareUrl(A, O.Fe.LINKEDIN_PROFILE), { context: F } = this.cacheService.getCacheOptions([v, z]), W = { force: this.isApiForce, fullName: "", version: `CE:${C.c.version}`, source: "LinkedIn", linkedin: z }; if (this.cognismUser.options.matchAdvancedEnabled && f.body) { const ft = RegExp(`<script[\s\S]*?<\/script>|${D.H}`, "g"), Nt = (0, t.replace)(f.body, ft, ""), Mt = (0, a.gzip)(Nt, { to: "string" }); W.html = btoa(Mt), W.htmlVersion = f.version } return this.http.post(v, W, { context: F }).pipe((0, u.T)(ft => this.cognismProfileMapService.mapToProfile(ft)))}401 Unauthorized: Your API Key does not appear to be valid
- X-Cognism-Client
- CE
- X-Cognism-Client-Version
- 4.0.26
- app.cognism.com
Primary Cognism application API host; main.js configures https://app.cognism.com/api and getProfileByHtml$ posts profile matching to /graph/person/view under this base URL.
- api.cognism.com
Secondary Cognism API host configured in main.js as https://api.cognism.com/api for newer profile metadata and related API calls.
Extension-wide headers and origin rewrite for Cognism API calls
Client headers added to every HTTP request
deobfuscated/main.jsintercept(c, O) { let H = c; return H = this.setApplicationHeaders(H), O.handle(H)}setApplicationHeaders(c) { return c.clone({ headers: c.headers.set("X-Cognism-Client", "CE").set("X-Cognism-Client-Version", v.c.version) })}Declarative rule sets the Origin header for Cognism requests
deobfuscated/rules.json[ { "id": 1, "priority": 1, "action": { "type": "modifyHeaders", "requestHeaders": [ { "header": "origin", "operation": "set", "value": "https://app.cognism.com" } ] }, "condition": { "urlFilter": "||cognism.com", "resourceTypes": [ "xmlhttprequest" ], "domains": [ "idfblidcbbfkggckamnibfbngnbgjocf" ] } }]