Is Friend Maker | Toolkit for Facebook safe?

High risk

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.

Levelsv1.4.18Chrome Web Store
72Risk

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
What actually happens
You did this

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 did this

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.

Fields assembled into the profile record
  • Facebook display name
    Jane Carter (illustrative)

    This identifies the Facebook profile being collected.

  • Facebook profile URL
    https://www.facebook.com/jane.carter.1987 (illustrative)

    This links the record to a specific Facebook account or profile page.

  • Mutual-friend context
    12 mutual friends (illustrative)

    This describes social-graph context around the collected profile.

  • Profile image URL
    https://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 status
    discovered

    This shows where the profile was in the extension workflow when it was stored.

  • Collection time
    2026-07-13T03:18:24.257Z (illustrative)

    This timestamps when the profile record was created or forwarded.

  • Record source label
    Facebook Profile Collector

    This labels the extension workflow that produced the record.

Captured request
POSThttps://webhook.site/0e814332-34c6-4819-bc8e-20432dce34cf
Headers
Content-Type
application/json
The code that does this

Profile collection, forwarding, and local save path in shipped code

Readable version

Readable background flow

background.js
async 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.js
async 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.js
async 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;}
External destination used by the profile POST
    • 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

windows
Updated 30 September 2026kddaagffkajgikddjngfbfblhcgmmlid