Is Dux-Soup for LinkedIn Automation safe?
Dux-Soup is high risk. With Dux-Soup installed, two page scripts wrap LinkedIn's XHR and Fetch calls, copying matching API responses into a CustomEvent named datachannel that the content script reads, exposing profile, search, mailbox, and messaging data.…
Who publishes itSupertec B.V. - no other listings under this identity, 2 shared hostnames
Supertec B.V. - no other listings under this identity, 2 shared hostnames
What this publisher told the store about itself, and the other listings that told it the same thing.
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
LinkedIn API responses forwarded through page events
With Dux-Soup installed, two page scripts wrap LinkedIn's XHR and Fetch calls, copying matching API responses into a CustomEvent named datachannel that the content script reads, exposing profile, search, mailbox, and messaging data.
- Severity
- High unwanted
- Type
- Unexpected
- CWE
- CWE-200
- Source
- Dynamic sandbox
You browse LinkedIn while the extension is installed.
The manifest runs client.js at document start on LinkedIn pages.
The extension inserts page scripts that copy matching LinkedIn API responses into a document event.
The event is named datachannel and contains the response body, content type, and response URL.
- LinkedIn response bodyJSON response from https://www.linkedin.com/voyager/api/ (illustrative)
Lets the extension read data LinkedIn returned to your browser: profile, search, mailbox, messaging, Sales Navigator, or Recruiter records.
- Response URLhttps://www.linkedin.com/sales-api/salesApiMailbox
Shows which LinkedIn area or record was loaded during your browsing session.
- Response typeapplication/json
Tells the extension whether the response was JSON or text before it forwards the copied body.
XHR wrapper copies matching LinkedIn responses into a document event
Same function in the deobfuscated tree
xhrforwarder.jsconst blocktarget = /\/(xhrforwarder|fetchforwarder|navoverride)\.js/function XHRForwarder(open){ XMLHttpRequest.prototype.open = function(method, url, async, user, pass) { var targets = [ "www.linkedin.com/communities-api/v1/memberships/community", "www.linkedin.com/sales/search/results", "www.linkedin.com/voyager/api/", "www.linkedin.com/connected/api/v2/contacts", "www.linkedin.com/connected/api/v1/contacts", "www.linkedin.com/sales-api/salesApiPeopleSearch", "www.linkedin.com/sales-api/salesApiLeadSearch", "www.linkedin.com/sales-api/salesApiCoach", "www.linkedin.com/recruiter/api/smartsearch", "www.linkedin.com/talent/search/api", "www.linkedin.com/talent/api", "www.linkedin.com/sales/profile/", "www.linkedin.com/sales-api/salesApiProfiles", "www.linkedin.com/sales-api/salesApiMessagingThreads", "www.linkedin.com/sales-api/salesApiMailbox"]; this.addEventListener("readystatechange", function() { if (this.readyState == 4 ) { var resp={}; try{ resp.data = this.response; resp.type = this.responseType; resp.url = this.responseURL; if ( typeof(resp.url)!="undefined" && targets.filter(target => resp.url.indexOf(target) >-1 ).length >0 ){ let resevent = new CustomEvent("datachannel", {detail:resp}); document.dispatchEvent(resevent); } }catch(e){ } } }, false); if ((""+url).match(blocktarget)){ url="chrome-extension://invalid/"; } open.call(this, method, url, async, user, pass); };};(XHRForwarder)(XMLHttpRequest.prototype.open);Fetch wrapper copies JSON or text responses before returning the original response
Same function in the deobfuscated tree
fetchforwarder.jsconst blocktarget = /chrome-extension:\/\/(.*?)\/(xhrforwarder|fetchforwarder|navoverride)\.js/function FETCHForwarder(fetchproto){ const targets = [ "www.linkedin.com/communities-api/v1/memberships/community", "www.linkedin.com/sales/search/results", "www.linkedin.com/voyager/api/", "www.linkedin.com/connected/api/v2/contacts", "www.linkedin.com/connected/api/v1/contacts", "www.linkedin.com/sales-api/salesApiPeopleSearch", "www.linkedin.com/sales-api/salesApiLeadSearch", "www.linkedin.com/sales-api/salesApiCoach", "www.linkedin.com/recruiter/api/smartsearch", "www.linkedin.com/talent/api", "www.linkedin.com/sales/profile/", "www.linkedin.com/sales-api/salesApiProfiles", "www.linkedin.com/sales-api/salesApiMessagingThreads", "www.linkedin.com/sales-api/salesApiMailbox", "www.linkedin.com/flagship-web/in"]; fetch = function(url, init){ if ((""+ url).match(blocktarget)){ url="chrome-extension://invalid/"; } return fetchproto(url, init).then(async function(res){ if ( typeof(res.url)!="undefined" && targets.filter(target => res.url.indexOf(target) > -1 ).length >0 ){ let resp={}; resp.type = res.headers.get("content-type"); resp.url = res.url; try{ if (resp.type=="application/octet-stream"){ resp.data = await res.clone().text(); }else{ resp.data = await res.clone().json(); } let resevent = new CustomEvent("datachannel", {detail:resp}); setTimeout(function(){ document.dispatchEvent(resevent); }, 1000); }catch(e){ // ignore, not json or text } } return res; }).catch((error)=>{ //ignore }); }}(FETCHForwarder)(fetch);Content script inserts the forwarder scripts into the LinkedIn page
Same functions in the deobfuscated tree
client.jsfunction inject(filename,payload){let s=document.createElement("script");s.src=chrome.runtime.getURL(filename);if(payload){s.src+="?pl="+encodeURIComponent(JSON.stringify(payload))}s.onload=function(){this.remove()};(document.head||document.documentElement).appendChild(s)}function injectXHRForwarder(){inject("xhrforwarder.js")}function injectFETCHForwarder(){inject("fetchforwarder.js")}function injectNAVOverride(){inject("navoverride.js")}- www.linkedin.com
Source of the matched API responses copied into datachannel events.
- app.dux-soup.com
Dux-Soup service host present in extension permissions and service-worker code; the forwarder scripts do not send directly to this host.
Cloud edition disables the standard Dux-Soup extension
Running as the Dux-Soup cloud or enterprise edition, this code checks whether the standard Dux-Soup extension is installed and enabled.
If present, the service worker calls Chrome's management API to turn that sibling extension off.
- Severity
- Medium unwanted
- Type
- Unexpected
- CWE
- CWE-940
- Source
- Dynamic sandbox
You install or start the cloud or enterprise edition while the standard Dux-Soup extension is also installed.
The extension checks for the standard edition and asks Chrome to disable it when it is enabled.
- Current edition IDnihpcockefpdcdfakndgdkggodikeldc
This value decides whether the disable routine runs at all.
- Target extension IDppdakpfeaodfophjplfdedpcodkdkbal
This is the standard Dux-Soup extension that the code asks Chrome to look up.
- Enabled flagextinfo.enabled === true
The sibling extension is only turned off when Chrome reports that it is currently enabled.
- Management permission"management"
This browser permission lets an extension inspect and change the enabled state of other installed extensions.
The service-worker function that disables the sibling edition
Readable service-worker code
sw.jsfunction checkAndDisableRegularDS() { try { chrome.management.get("ppdakpfeaodfophjplfdedpcodkdkbal", function(extinfo) { if (extinfo != null && extinfo.enabled) { chrome.management.setEnabled(extinfo.id, false) } else if (chrome.runtime.lastError) {} }) } catch (e) {}}if (chrome.runtime.id == "nihpcockefpdcdfakndgdkggodikeldc") { checkAndDisableRegularDS()}Readable manifest permission excerpt
manifest.jsonpermissions: [ "tabs", "identity", "webRequest", "declarativeNetRequest", "power", "idle", "downloads", "storage", "management", "cookies", "offscreen"]