Is Free B2B Contact Info by AroundDeal safe?
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.
Who publishes itAROUNDDEAL HOLDINGS LIMITED - no other listings under this identity
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.
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
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 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.
- The page you visitedhttps://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 tokeneyJhbGciOiJIUzI1NiJ9.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.
manifest.json grants all-site access; content_scripts only runs on LinkedIn
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.jschrome.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) {}}- 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.
Scans an unpacked extension for host_permissions wider than content_scripts, paired with a tabs.onUpdated listener that fetches out on every navigation.
- Node.js 18+
#!/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();- 1Save as detect-unscoped-nav-listener.js.
- 2Run: node detect-unscoped-nav-listener.js ./unpacked-extension.
- 3[FLAG] + exit code 2 means the mismatch was found.
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.