Is Friend Requests Sender - Send, Track, Cancel & Auto Add Friends safe?
Friend Requests Sender collects Facebook users' email, phone, address, and birthday from imported profiles, undisclosed in its listing.
When you paste or upload a list of Facebook profile IDs to send friend requests to, the extension also fetches each profile's 'Contact and basic info' page in the background, disguising the request as a normal page visit to avoid detection. It parses out each person's email, phone/WhatsApp number, address, hometown, birthday, and relationship status, stores the results locally, and shows them in table columns you can export to a CSV file. None of this contact-harvesting is mentioned in the extension's Chrome Web Store description, which only advertises sending, tracking, and canceling friend requests.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Friend Requests Sender pulls email, phone and birthday off imported profiles
Code analysis shows that importing a Facebook profile fetches that person's private contact info in the background and saves their email, phone, address and birthday, none of which the store listing discloses.
You paste a Facebook profile URL or ID into the extension's 'Import New' dialog.
This adds that person to your bulk friend-request list.
The extension fetches that profile's private contact info in the background and saves their email, phone, address and birthday to your device.
The request's headers are rewritten to match a real page load, avoiding Facebook's automation checks.
Rewriting the profile URL and spoofing navigation headers before the fetch
// Tee(profileUrl): rewrite a normal profile URL to Facebook's
// "Contact and basic info" endpoint before it's fetched.
function Tee(profileUrl) {
const url = new URL(profileUrl);
if (url.searchParams.has("id")) {
// profile.php?id=123 -> ...&sk=directory_contact_info
url.searchParams.set("sk", "directory_contact_info");
} else {
// /username -> /username/directory_contact_info
url.pathname = `${url.pathname.replace(/\/+$/, "")}/directory_contact_info`;
}
return url.toString();
}// M(profileUrl): runs in the service worker. Installs a temporary
// declarativeNetRequest rule that rewrites this one request's headers
// to look like a real browser page load, fetches with the logged-in
// user's own Facebook cookies attached, then removes the rule.
async function M(profileUrl, timeoutMs = 15000) {
const ruleId = Math.floor(Date.now() % 1e6) + Math.floor(1000 * Math.random());
const rule = {
id: ruleId,
priority: 100,
action: {
type: "modifyHeaders",
requestHeaders: [
{ header: "sec-fetch-mode", value: "navigate", operation: "set" },
{ header: "sec-fetch-dest", value: "document", operation: "set" },
{ header: "accept", value: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", operation: "set" },
],
},
condition: {
urlFilter: `||${new URL(profileUrl).host}/`,
resourceTypes: ["xmlhttprequest"],
tabIds: [chrome.tabs?.TAB_ID_NONE ?? -1],
},
};
await chrome.declarativeNetRequest.updateSessionRules({ removeRuleIds: [ruleId], addRules: [rule] });
try {
const response = await fetch(profileUrl, { credentials: "include" });
return await response.text();
} finally {
await chrome.declarativeNetRequest.updateSessionRules({ removeRuleIds: [ruleId] });
}
}| Field | Value | Why it matters | |
|---|---|---|---|
Email address | d.alvarez1988@gmail.com | The personal email tied to their account, collected without their knowledge. | |
Phone / WhatsApp number | +1 786 555 0134 | A direct line to the person, unrelated to sending them a friend request. | |
Current city | Miami, Florida | Where they currently live, day to day. | |
Hometown | San Juan, Puerto Rico | Where they grew up, a common bank and email security-question answer. | |
Birthday | March 14, 1988 | Full date of birth, another common account-recovery answer. | |
Relationship status | In a relationship | A personal-life detail scraped alongside their contact info. |
Saving a harvested record for every imported ID, then exporting it
// w(ids, status): runs once per profile ID pasted or uploaded into
// 'Import New'. Fetches that profile's harvested record and stores it.
async function importProfiles(ids, status) {
ids = ids.filter(Boolean);
if (ids.length === 0) return;
await forEachWithDelay(ids, async (id) => {
if (existingRows.find((row) => row.id === id)) return;
const details = await Kee({ id, name: "", url: `https://www.facebook.com/profile.php?id=${id}` });
const current = await profileStore.get();
if (current.find((row) => row.id === id)) return;
await profileStore.set([...current, { id, status, log: "", details, selected: false }]);
});
setStatus("Ready");
}// Maps one saved profile row onto the spreadsheet columns produced
// by the extension's CSV/XLSX export button.
function toExportRow(row) {
const d = row.details;
return {
ID: d?.id,
Name: d?.name,
Gender: d?.gender,
Birthday: d?.birthday,
Relationship: d?.relationship || "",
Hometown: d?.hometown?.name || "",
Location: d?.location?.name || "",
Bio: d?.bio || "",
Category: d?.category || "",
Email: d?.email || "",
"Phone Number": d?.mobile_phone || "",
Websites: d?.websites?.join(", ") || "",
};
}The CWS listing advertises only bulk friend-request sending and says 'all data remains on your device.' It never discloses that importing a profile also harvests that person's own contact details.
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.