Is Email Finder-Kendo Sourcing Ninja safe?
Kendo Sourcing Ninja is high risk. The extension posts to kendoemailapp.com/kendoquerystatus2 when a LinkedIn profile loads, carrying the profile slug and fields name, company, title, company ID, member ID, user, location, platform, related companies, version, checksum.…
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
LinkedIn profile fields posted on profile visits
The extension posts to kendoemailapp.com/kendoquerystatus2 when a LinkedIn profile loads, carrying the profile slug and fields name, company, title, company ID, member ID, user, location, platform, related companies, version, checksum.
You open a LinkedIn member profile page.
The observed test page was the LinkedIn profile for williamhgates.
The extension posts the viewed profile identifier and profile fields to Kendo.
Dynamic analysis observed three POST requests after the profile page loaded.
| Field | Value | Why it matters | |
|---|---|---|---|
Viewed profile | urls=williamhgates | Identifies the LinkedIn member profile you opened. | |
Profile identity fields | nm="", comp="", title="", loc="" in the recorded request | Can include the person's displayed name, title, company, company ID, member ID, and location when those values are available on the page. | |
LinkedIn context | platform="", comps="" | Adds context about the profile source and related companies so the profile visit can be categorized. | |
Extension metadata | v=6.702 plus crc | Ties the request to the installed extension version and a checksum generated by the extension. |
| Accept | application/json, application/xml, text/plain, text/html, *.* |
| Content-Type | application/json |
Profile visit path from content script to Kendo
chrome.runtime.sendMessage({
contentScriptQuery: _e.getstatus,
data: {
urls: i,
nm: c,
comp: m,
title: u,
compid: p,
id: f,
usr: h,
loc: g,
platform: b,
comps: y
}
}, function(r) {
chrome.runtime.sendMessage({ contentScriptQuery: _e.gettop }, function(a) {
if (r) {
var v = "";
r && r.found && r.status && (v = r.status);
var x = document.createElement("div");
x.id = "kendo_elem";
}
});
});if (t.contentScriptQuery == o.getstatus) {
t.data && (t.data.v = chrome.runtime.getManifest().version);
j = i(JSON.stringify(t.data));
return t.data.crc = j, fetch("https://kendoemailapp.com/kendoquerystatus2", {
method: "POST",
headers: {
Accept: "application/json, application/xml, text/plain, text/html, *.*",
"Content-Type": "application/json"
},
body: JSON.stringify(t.data)
}).then(function(t) {
return t.json();
}).then(function(t) {
!c && t && t.widgettop && t.widgettop > 20 && (c = t.widgettop), n(t);
}).catch(function(t) {
return console.log("Error:", t);
}), !0;
}- kendoemailapp.com
Receives LinkedIn profile-status requests at /kendoquerystatus2.
Saved LinkedIn profile records posted to Kendo
Pressing save builds a LinkedIn profile record sent to kendoemailapp.com/kendoquery.
Fields can include URL, notes, status, subject, message, member name, pools, rate, mobile, website, email, title, skills, location, salary, memberID.
You save a LinkedIn profile with the extension.
The save button is disabled until the member-name field has content.
The extension sends a saved-profile record to Kendo.
The record combines values entered in the extension UI with fields read from the LinkedIn page.
| Field | Value | Why it matters | |
|---|---|---|---|
Profile URL and IDs | url=https://www.linkedin.com/in/jane-recruiter, memberID=123456789 (illustrative) | Identifies the exact LinkedIn profile saved and links the record to LinkedIn member and talent IDs. | |
Entered recruiting notes | notes=Follow up next week, status=Contacted, salary=120000 (illustrative) | Carries the notes, status, subject, message body, pool, assignment, rate, and salary values entered while saving the profile. | |
Contact fields | workemail=person@example.com, mobile=+1-415-555-0142 (illustrative) | Can include work email, personal email, phone number, website, and the saved person's displayed name. | |
Profile page details | title=Engineering Manager, location=San Francisco, imageURL=https://media.licdn.com/profile.jpg (illustrative) | Adds LinkedIn page details such as title, company, location, skills, current job, and profile image URL. |
| Accept | application/json, application/xml, text/plain, text/html, *.* |
| Content-Type | application/json |
Save button path from content script to Kendo
onClick: function() {
B(!0);
var e = ke(Ye) ? Vi : Ye,
n = Oe.filter(function(e) { return e.active; }).map(function(e) { return "".concat(e.text); }),
r = n.join("~"),
a = Me(window.location.href),
o = {
notes: we,
url: ue,
status: e,
skiilsyears: oe,
subject: nt,
body: ot,
membername: K,
pools: r,
rate: null != Ke ? Ke : 0,
mobile: P,
website: I,
___pools: n,
workemail: C,
email: M,
title: Te,
assigned: $,
skills: ne,
location: le,
job: S,
salary: A,
talentid: a
},
s = document.querySelector("[data-member-id]");
if (s && (o.memberID = s.attributes["data-member-id"].value), ((s = document.querySelector('[aria-label="Current company"]')) || (s = document.querySelector("[data-test-position-entity-company-link]"))) && (o.memberCompany = s.innerText.trim()), 1 == (s = document.getElementsByClassName("pv-top-card-profile-picture__image")).length) {
var l = s[0].attributes.getNamedItem("src");
l && (o.imageURL = l.value);
}
chrome.runtime.sendMessage({
contentScriptQuery: _e.postdata,
data: o
}, function(r) {
v(e), Ie(n), i(!1), B(!1);
var a = new CustomEvent("profilechanged", {
detail: { status: Ye, id: Le, pools: n, rate: Ke, assigned: $, nm: K, sk: ne, sky: oe }
}),
o = document.getElementById("kendoplugin");
o && o.dispatchEvent(a), t.onClose && t.onClose(Ye);
});
}if (t.contentScriptQuery == o.postdata) return t.data && (t.data.v = chrome.runtime.getManifest().version), fetch("https://kendoemailapp.com/kendoquery", {
method: "POST",
headers: {
Accept: "application/json, application/xml, text/plain, text/html, *.*",
"Content-Type": "application/json"
},
body: JSON.stringify(t.data)
}).then(function(t) {
return t.json();
}).then(function(t) {
n(t);
}).catch(function(t) {
return console.log("Error:", t);
}), !0;- kendoemailapp.com
Receives saved LinkedIn profile records at /kendoquery.
LinkedIn profile details sent in a Kendo query URL
A LinkedIn profile update-button click sends its URL, candidate ID, company ID, and name to the background script, which GETs kendoemailapp.com/kendoquery2 with those as parameters.
Unauthenticated DA couldn't capture this; needs a login.
You press the extension update button on a LinkedIn profile.
The extension sends profile identifiers and the displayed name to Kendo in URL query parameters.
| Field | Value | Why it matters | |
|---|---|---|---|
LinkedIn profile URL | https://www.linkedin.com/in/williamhgates | This identifies the LinkedIn profile page you were viewing when you clicked the extension button. | |
Displayed profile name | William H. Gates | This can identify the LinkedIn member associated with the lookup. | |
Candidate ID | 18492517 | This links the request to the extension or Kendo record for that profile. | |
Company ID | 1035 | This adds company context to the profile lookup. |
Click handler and Kendo query construction
handleShow: function() {
var e;
(t.memberurl || t.candidateID) && (chrome.runtime.sendMessage({
contentScriptQuery: _e.allcandidates
}, function(e) {
ut(e)
}), t.candidateID && Ce(t.candidateID), U(!0), chrome.runtime.sendMessage({
contentScriptQuery: _e.getdata,
url: t.memberurl,
id: t.candidateID,
cmp: null !== (e = t.memberCompanyID) && void 0 !== e ? e : -1,
nm: t.memberName
}, function(e) {
if (U(!1), null != e && "" != e) {
var x = JSON.parse(e);
if (x.needlogin) return b(!0);
}
}))
}if (t.contentScriptQuery == o.getdata) return fetch("https://kendoemailapp.com/kendoquery2?url=".concat(encodeURIComponent(null !== (a = t.url) && void 0 !== a ? a : ""), "&id=").concat(encodeURIComponent(null !== (s = t.id) && void 0 !== s ? s : ""), "&cmp=").concat(encodeURIComponent(null !== (l = t.cmp) && void 0 !== l ? l : ""), "&mid=").concat(encodeURIComponent(null !== (m = t.memberid) && void 0 !== m ? m : ""), "&nm=").concat(encodeURIComponent(null !== (u = t.nm) && void 0 !== u ? u : ""))).then(function(t) {
return t.text()
}).then(function(t) {
return n(t)
}).catch(), !0;- kendoemailapp.com
Receives the profile lookup GET request at /kendoquery2 with LinkedIn profile fields in the query string.
+3 more findings not shown