Is Friend Disconnector safe?
Friend Disconnector sends Facebook friend profiles and activity metrics to api.frienddisconnector.com.
Friend Disconnector runs on Facebook pages, queries Facebook's GraphQL endpoints for friend records, and builds profiles with names, Facebook IDs, profile URLs, avatars, location fields, messages, reactions, comments, activity status, and engagement counts. It stores those records locally and sends them to api.frienddisconnector.com, and it also sends friend names to an AWS Lambda endpoint to infer gender, country, or tier data.
Who publishes itTier5 Technologies - 5 other listings from the same operator, none carrying a finding
Tier5 Technologies - 5 other listings from the same operator, none carrying a finding
What this publisher told the store about itself, and the other listings that told it the same thing.
Same store account
5 other listings published from this account, 9k+ users between them, none of them carrying a finding.
Shared hosts - 1 hostname
Hostnames hardcoded in this extension that few other listings call. That can mean one operator behind both, and it can equally mean a small shared vendor, so it is context rather than a conclusion. Hosts that many listings call are left out: they are services, not connections.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Facebook friend graph uploaded to Friend Disconnector
Friend Disconnector's Facebook sync reads friend records from GraphQL responses, then the background script sends them, including profile IDs, URLs, avatars, location, and engagement counts, to api.frienddisconnector.com/friends.
- Severity
- High unwanted
- Type
- Unexpected
- CWE
- CWE-359
- Source
- Dynamic sandbox
You start Friend Disconnector's Facebook friend sync or analyze flow.
The extension only runs its content script on facebook.com pages.
The extension builds friend records and sends them to Friend Disconnector's API.
Each record can include profile identifiers, profile URLs, avatar URLs, locations, and engagement counts.
- Friend nameAlex Morgan (illustrative)
Identifies a person in your Facebook friend list.
- Facebook ID100064789123456 (illustrative)
Links the record to a specific Facebook account.
- Profile URLhttps://www.facebook.com/100064789123456 (illustrative)
Lets the recipient open or correlate the friend profile.
- Avatar URLhttps://scontent.xx.fbcdn.net/v/t39.30808-1/349123456_123456789012345_987654321_n.jpg (illustrative)
Adds a profile image URL to the person record.
- Location fieldsCanada / Toronto (illustrative)
Adds country or hometown context from the Facebook profile.
- Activity metricsmessages: 4, reactions: 6, comments: 2, engagements: 8 (illustrative)
Shows how often you have interacted with that friend.
- Activity flagshasConversation: true, isActive: true (illustrative)
Marks whether the friend appears active or has a conversation history with you.
- Fetcher ID100012345678901 (illustrative)
Links the uploaded friend record back to your Facebook account running the sync.
Friend records are built in the content script and uploaded by the background script
Readable equivalent of FBGraphQLService.fetchFriends
content.jsstatic fetchFriends(accessToken, fetcherId, fbDtsg, cursor, includeActivity = false, createNew = true, retryFlag = "no") { return new Promise(async (resolve, reject) => { try { if (includeActivity) { await delay(1000); await this.fetchExcludedPostsIdNew(fetcherId, fbDtsg, null); } const form = { av: fetcherId, __user: fetcherId, __a: 1, dpr: 1, fb_dtsg: fbDtsg, fb_api_caller_class: "RelayModern", fb_api_req_friendly_name: "FriendingCometFriendsListPaginationQuery", variables: JSON.stringify({ count: 30, cursor, name: null, scale: 1 }), server_timestamps: true, doc_id: 0xf2a65945cd96b }; const response = await fetch("https://www.facebook.com/api/graphql/", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded", Accept: "text/html,application/json", "x-fb-friendly-name": "FriendingCometFriendsListPaginationQuery" }, body: this.serializeFBReq(form) }); const graph = await response.json(); if (!isEmpty(graph) && graph.hasOwnProperty("data") && graph.data.hasOwnProperty("viewer") && graph.data.viewer.hasOwnProperty("all_friends")) { if (graph.data.viewer.all_friends.hasOwnProperty("edges")) { graph.data.viewer.all_friends.edges.forEach(async (edge) => { if (!isEmpty(edge)) { const { id: friendId, gender, name, image, profile_picture, location, hometown } = edge.node; if (edge.node.id) { const avatar = !isEmpty(image) && image.hasOwnProperty("uri") && image.uri ? image.uri : !isEmpty(profile_picture) && profile_picture.hasOwnProperty("uri") && profile_picture.uri ? profile_picture.uri : ""; const profileUrl = `https://www.facebook.com/${friendId}`; const rawCountry = location && location.hasOwnProperty("location") && location.location && location.location.hasOwnProperty("country") ? location.location.country : null; const hometownName = hometown && hometown.hasOwnProperty("name") ? hometown.name.split(", ").slice(-1).pop() : ""; const messages = await this.getMessageCount(fbDtsg, fetcherId, friendId); const reactions = this.reactors.hasOwnProperty(friendId) ? this.reactors[friendId] : 0; const comments = this.commenters.hasOwnProperty(friendId) ? this.commenters[friendId] : 0; const hasConversation = messages > 2; const isActive = messages > 2 || reactions > 0 || comments > 0; const engagements = reactions + comments; const foundAt = new Date().getTime(); let normalizedLocation = { tier: "", country: "" }; if (rawCountry || hometownName) normalizedLocation = await normalizeCountry(rawCountry || hometownName); const friend = { name, fbId: friendId, fetcherId, url: profileUrl, avatar, isDeactivated: false, gender, country: normalizedLocation.country, hometown: hometownName, messages, reactions, comments, hasConversation, isLost: false, isActive, engagements, foundAt, profilePictureUrl: avatar, isWhitelisted: false, isUnfriended: false, tier: normalizedLocation.tier, finalSync: 0, sync: 0 }; this.friends[friendId] = friend; this.friendsForSync.push(friend); chrome.runtime.sendMessage({ type: MessageTypes.SEND_FRIENDS_TO_BG, data: friend, createNew }); await delay(500); } } }); await delay(1000); this.friendsForSync = []; } if (graph.data.viewer.all_friends.hasOwnProperty("page_info") && graph.data.viewer.all_friends.page_info.hasOwnProperty("has_next_page") && graph.data.viewer.all_friends.page_info.has_next_page && graph.data.viewer.all_friends.page_info.end_cursor) { return resolve(this.fetchFriends(accessToken, fetcherId, fbDtsg, graph.data.viewer.all_friends.page_info.end_cursor, false, createNew, retryFlag)); } if (await delay(30000), await delay(1000), retryFlag === "no") { return resolve(this.fetchFriends(accessToken, fetcherId, fbDtsg, null, false, createNew, "yes")); } resolve(); } } catch (error) { reject(error); } });}Readable equivalent of FriendService.sync
background.jsstatic sync(token, friends, isFresh, fetcherId) { return new Promise(async (resolve, reject) => { try { await this.http.authorization(token).post("friends", { friends, isFresh, fetcherId }); resolve(); } catch (error) { reject(error); } });}- www.facebook.com
Source of the GraphQL friend-list data read by the content script.
- api.frienddisconnector.com
Receives the friend records through the /friends API path.
What it can do
Permissions this extension asks for, as declared in version 2.0.50. 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 api.frienddisconnector.com
https://api.frienddisconnector.com/*
Read and change your data on amazonaws.com
https://*.amazonaws.com/*
Read and change your data on frienddisconnector.com
*://*.frienddisconnector.com/*
Schedule its own background tasks
alarms
Store data in your browser
storage
Store an unlimited amount of data in your browser
unlimitedStorage
Where it sends data
Destinations our analysis observed Friend Disconnector 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.
- api.frienddisconnector.com
Friend Disconnector sends data to api.frienddisconnector.com. 3 other extensions we have analysed send data here.
- z7c5j0fjy8.execute-api.us-east-2.amazonaws.com
Friend Disconnector sends data to z7c5j0fjy8.execute-api.us-east-2.amazonaws.com. One other extension we have analysed sends data here.