Is Free B2B Contact Info by AroundDeal safe?

Medium risk

AroundDeal's B2B Contact Info extension reports every site you visit, not just LinkedIn, to its own backend while you're signed in.

Whenever a browser tab finishes loading a page, the extension checks whether the user is signed in to AroundDeal (via a session cookie) and, if so, sends that page's URL to AroundDeal's servers — for any site, not only LinkedIn where the extension's contact-lookup feature actually runs. The request carries the user's AroundDeal auth token and the visited URL; the extension discards the response. This is enabled by broad host permissions (all HTTP/HTTPS sites) that go beyond what the LinkedIn-only contact-lookup feature needs.

AroundDeal.comv9.1.1Chrome Web Store
49Risk
Who publishes it

AROUNDDEAL HOLDINGS LIMITED - no other listings under this identity

What this publisher told the store about itself, and the other listings that told it the same thing.

Publisher
AroundDeal.com
Declared legal entity
AROUNDDEAL HOLDINGS LIMITED
Registered contact
Ferris Xu

AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.

Publishers can request a review.

Findings

AroundDeal sends every site you visit to its backend while you're signed in

Code analysis shows AroundDeal's background worker sends the URL of every website you visit, not only LinkedIn, to AroundDeal's own servers whenever you're signed in to your AroundDeal account.

Severity
High unwanted
Type
Unexpected
CWE
CWE-359
Source
Static analysis
What actually happens
You did this

You navigate to any website that is not LinkedIn or AroundDeal while signed in to your AroundDeal account.

This includes ordinary browsing unrelated to LinkedIn or B2B contact lookup.

The extension did this

The background worker reads your AroundDeal session cookie and posts the visited URL to AroundDeal's own backend with that token attached.

The response body is discarded; the request exists only to report the visit.

What gets sent on every non-LinkedIn page load
  • The page you visited
    https://www.nytimes.com/2026/09/05/technology/ai-chips.html

    The full URL of the site you were on when the check fired, tied to your AroundDeal account.

  • Your AroundDeal session token
    eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOiI4OTEyMzQiLCJhY2NvdW50IjoiYWNtZS1jb3JwIn0.k3Jm2v8QY7z1pNc4T9xLwR

    A bearer credential read from your browser's cookies and attached to every check so AroundDeal can tie the visit to your account.

The code that does this

manifest.json grants all-site access; content_scripts only runs on LinkedIn

Readable version

manifest.json: host_permissions vs content_scripts scope

manifest.json
"host_permissions": [  "https://*/*",  "http://*/*"],..."content_scripts": [  {    "matches": [      "*://*.linkedin.com/*"    ],    "js": [      "utils/auth.js",      "utils/reserved-permissions.js",      "api/api.js",      "components/unlock-card.js",      "components/drawer.js",      "content/content.js"    ],    "css": [      "components/unlock-card.css",      "components/drawer.css",      "content/content.css"    ],    "run_at": "document_end"  }]

background/background.js: the unscoped navigation listener

background/background.js
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {  if (changeInfo.status === 'complete' && tab.url) {    if (tab.url.includes('linkedin.com')) {      chrome.tabs.sendMessage(tabId, {        action: 'pageLoaded',        url: tab.url      }).catch(() => {});    } else {      checkNonTargetSite(tab.url);    }  }});async function checkNonTargetSite(url) {  try {    if (!url ||        url.startsWith('chrome://') ||        url.startsWith('chrome-extension://') ||        url.startsWith('about:') ||        url.includes('linkedin.com') ||        url.includes('arounddeal.com')) {      return;    }    const token = await getTokenFromCookie();    if (!token) {      return;    }    fetch('https://ep.arounddeal.com/v1/check', {      method: 'POST',      headers: {        'Authorization': `Bearer ${token}`,        'Content-Type': 'application/json'      },      body: JSON.stringify({ url: url })    }).catch(() => {});  } catch (error) {}}
Where the visited-page data goes
    • ep.arounddeal.com

    AroundDeal's own backend. Receives the URL of every non-LinkedIn, non-AroundDeal site visited plus a bearer token identifying the signed-in account.

Check if you're affected

Scans an unpacked extension for host_permissions wider than content_scripts, paired with a tabs.onUpdated listener that fetches out on every navigation.

Requires
  • Node.js 18+
detect-unscoped-nav-listener.js · js
#!/usr/bin/env node/** * detect-unscoped-nav-listener.js * * Flags extensions whose declared content-script scope (manifest.json * "content_scripts"[].matches) is far narrower than their granted * host_permissions, while the background script registers a * chrome.tabs.onUpdated listener that fetches out to a remote host on * every navigation. That combination is the shape used to report a * user's full browsing history to a vendor's own backend under the * guise of a single-site tool. * * Usage: *   node detect-unscoped-nav-listener.js <path-to-unpacked-extension> * * Requires: Node.js 18+ */const fs = require('fs');const path = require('path');function readJSON(file) {  return JSON.parse(fs.readFileSync(file, 'utf8'));}function findBackgroundFile(root, manifest) {  const sw = manifest.background && manifest.background.service_worker;  if (!sw) return null;  const p = path.join(root, sw);  return fs.existsSync(p) ? p : null;}function contentScriptHosts(manifest) {  const scripts = manifest.content_scripts || [];  const hosts = new Set();  for (const cs of scripts) {    for (const m of cs.matches || []) hosts.add(m);  }  return [...hosts];}function isBroadPermission(p) {  return p === '<all_urls>' || /^https?:\/\/\*\/\*$/.test(p);}function main() {  const root = process.argv[2];  if (!root) {    console.error('Usage: node detect-unscoped-nav-listener.js <extension-dir>');    process.exit(1);  }  const manifestPath = path.join(root, 'manifest.json');  if (!fs.existsSync(manifestPath)) {    console.error(`No manifest.json at ${manifestPath}`);    process.exit(1);  }  const manifest = readJSON(manifestPath);  const hostPerms = manifest.host_permissions || [];  const csHosts = contentScriptHosts(manifest);  const broadHostPerms = hostPerms.filter(isBroadPermission);  const scopeMismatch =    broadHostPerms.length > 0 &&    csHosts.length > 0 &&    !csHosts.some(isBroadPermission);  const bgFile = findBackgroundFile(root, manifest);  let listenerHit = false;  let fetchHosts = [];  if (bgFile) {    const src = fs.readFileSync(bgFile, 'utf8');    const hasOnUpdated = /chrome\.tabs\.onUpdated\.addListener/.test(src);    const hasFetch = /fetch\s*\(/.test(src);    listenerHit = hasOnUpdated && hasFetch;    const urlRe = /fetch\(\s*['"`](https?:\/\/[^'"`/]+)/g;    let m;    while ((m = urlRe.exec(src))) fetchHosts.push(m[1]);    fetchHosts = [...new Set(fetchHosts)];  }  console.log(`Extension: ${manifest.name || '(unknown)'} v${manifest.version || '?'}`);  console.log(`host_permissions: ${hostPerms.join(', ') || '(none)'}`);  console.log(`content_scripts matches: ${csHosts.join(', ') || '(none)'}`);  console.log(`tabs.onUpdated + fetch in background: ${listenerHit}`);  console.log(`fetch() destinations seen in background: ${fetchHosts.join(', ') || '(none)'}`);  if (scopeMismatch && listenerHit) {    console.log('\n[FLAG] host_permissions are unscoped (all sites) while content_scripts');    console.log('       are limited to a single site, and the background worker fetches');    console.log('       out to a remote host on every navigation. This shape lets the');    console.log('       extension observe and report every site you visit, not just the');    console.log('       one its content script runs on. Read the onUpdated handler in');    console.log(`       ${bgFile} to see what it sends and where.`);    process.exitCode = 2;  } else {    console.log('\nNo mismatch of this shape detected.');  }}main();
How to run it
  1. 1Save as detect-unscoped-nav-listener.js.
  2. 2Run: node detect-unscoped-nav-listener.js ./unpacked-extension.
  3. 3[FLAG] + exit code 2 means the mismatch was found.
Observation

Static analysis finding. This behaviour was identified by reading the shipped extension code and has not yet been reproduced in a live run. The trigger conditions and the exact data sent are read from the code, not from an observed capture.

Where it sends data

Destinations our analysis observed Free B2B Contact Info by AroundDeal contacting. Sending data somewhere is not a finding on its own - an extension that syncs your settings has to talk to its own server - but it is where your data can go, and who else it goes to.

  • ep.arounddeal.com

    Free B2B Contact Info by AroundDeal sends data to ep.arounddeal.com. No other extension we have analyzed sends data here.

Updated 30 September 2026fghbfikeodjkbajmokiekipabfckcpgc