Is Cognism - Premium Sales Intelligence safe?

High risk

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.…

Cognismv4.0.34Chrome Web Store
78Risk
Who publishes it

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.

Publisher
Cognism
Declared legal entity
COGNISM Limited
Registered address
150 Minories, Worldwide Corporate Advisors, London EC3N 1LS, GB
Registered contact
Stjepan Buljat

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.

app.cognism.com
Also called by 1 other listing: HG Insights Sales Copilot
cognism.com
Also called by 3 other listings, including Mailtastic

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
What actually happens
You did this

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 extension did this

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.

Fields returned by the DOM collection path
  • 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 context
    https://www.linkedin.com/in/williamhgates

    This ties the collected markup to the specific supported record, profile, company, or search page you were viewing.

Captured request
GEThttps://app.cognism.com/api/users

401 Unauthorized during dynamic analysis; the DOM collection path was not reached in that session.

The code that does this

The shipped content script reads full page markup and returns it over extension messaging

Readable version

The popup-side helper requests body HTML from the content script

common.js
getPageOuterHtml$(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.js
getDomPage(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.js
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  })}

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
What actually happens
You did this

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 extension did this

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.

Session-backed request fields and returned data families
  • LinkedIn CSRF value
    ajax:8392746501928374650

    This lets the request use the active LinkedIn web session from your browser.

  • LinkedIn session cookies
    li_at=redacted-session-cookie; JSESSIONID="ajax:8392746501928374650"

    These tie the API request to the LinkedIn account currently signed in on the page.

  • Profile details
    William 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 URL
    https://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.

Captured request
GEThttps://www.linkedin.com/sales-api/salesApiProfiles/(profileId:ACwAAAEAAAA,authType:NAME_SEARCH,authToken:abc123)

Request shape from shipped source code; dynamic analysis recorded zero sales-api or talent-api requests because authentication gates blocked the workflow.

Headers
csrf-token
ajax:8392746501928374650
x-restli-protocol-version
2.0.0
Captured request
POSThttps://www.linkedin.com/talent/api/talentLinkedInMemberProfiles/urn%3Ali%3Ats_linkedin_member_profile%3A(williamhgates%2C1%2Curn%3Ali%3Ats_hiring_project%3A(urn%3Ali%3Ats_contract%3A123456%2C7890))

Request shape from shipped source code; dynamic analysis recorded zero sales-api or talent-api requests because authentication gates blocked the workflow.

Headers
csrf-token
ajax:8392746501928374650
x-http-method-override
GET
x-restli-protocol-version
2.0.0
The code that does this

The shipped content script builds LinkedIn API requests with the browser session

Readable version

CSRF token comes from the LinkedIn JSESSIONID cookie

content.js
const ns = () => {  const o = (document.cookie.match(/JSESSIONID=(.*?)(?:;|$)/) || [])[1] || "";  return (0, F.replace)(o, /"/g, "")};

Recruiter profile request uses csrf-token and withCredentials

content.js
getRecruiterProfile$(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.js
getSalesNavigatorProfile$(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.js
getSalesNavigatorProfileSearch$(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))}
Hosts involved in the verified code path
    • 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
What actually happens
You did this

You view a LinkedIn profile while using the Cognism extension.

The extension did this

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.

Fields built for the profile-matching request
  • LinkedIn profile URL
    https://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 version
    CE:4.0.26

    This tells Cognism which extension build prepared the request.

  • Source label
    LinkedIn

    This marks the request as coming from a LinkedIn profile workflow.

The code that does this

The shipped functions that gather page HTML and post it to Cognism

Readable version

LinkedIn profile route gathers HTML and calls profile matching

deobfuscated/928.js
get 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.js
getPageOuterHtml$(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.js
getDomPage(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.js
getProfileByHtml$(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)))}
Captured request
GEThttps://app.cognism.com/api/users?force=true

401 Unauthorized: Your API Key does not appear to be valid

Headers
X-Cognism-Client
CE
X-Cognism-Client-Version
4.0.26
Cognism-owned API destinations named by the shipped code
    • 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.

The code that does this

Extension-wide headers and origin rewrite for Cognism API calls

Readable version

Client headers added to every HTTP request

deobfuscated/main.js
intercept(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"      ]    }  }]
Updated 30 September 2026idfblidcbbfkggckamnibfbngnbgjocf