Is Novalya safe?
Novalya extracts a Facebook OAuth access token, fetches friend profile details, and uploads them to api-v2.novalya.com.
When Facebook friend syncing runs, the extension opens an Instagram OAuth flow, extracts a Facebook access token from the browser response, and uses it to request friend records from graph.facebook.com. It sends details such as names, birthdays, emails, phone numbers, genders, locations, languages, profile links, locales, and pictures to api-v2.novalya.com under the user's Novalya auth token.
Who publishes itNOVALLIANCE LIMITED - no other listings under this identity
NOVALLIANCE 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
Uses Facebook token to upload friend profiles
Starting Novalya's Facebook friend sync opens an Instagram tab, requests OAuth status via signed-in cookies, and reads the access token from the response header.
It queries the Graph API for friend fields, then posts records to Novalya.
- Severity
- High unwanted
- Type
- Unexpected
- CWE
- CWE-522
- Source
- Dynamic sandbox
You click the Facebook friend profile sync button in Novalya.
The content script registers this on the #sync_friends_profiles click handler.
The extension retrieves a Facebook bearer token, queries Facebook for friend details, and posts the profile records to Novalya.
The token is used for Graph API access; the friend profile payload is sent to api-v2.novalya.com.
- Facebook access tokenEAABsbCS1iHgBO2vPq9mF3xYh8K2nL4rT7wQ6zA5 (illustrative)
This lets the extension make Facebook Graph API requests as the signed-in browser session during the sync.
- Facebook friend ID100064735918245
This identifies a person in the selected Facebook friend list.
- NameMarie Dupont
This can directly identify the friend whose profile was requested.
- Birthday and age rangebirthday=04/18/1990; age=30-40
This adds personal details about the friend profile beyond the friend connection itself.
- Email and phonemarie.dupont@example.fr; +33 6 12 34 56 78
These are direct contact details when Facebook returns them for a friend profile.
- Location and languagehometown=Paris; location=Lyon; languages=French, English; locale=fr_FR
This adds profile context such as hometown, current city, language, and locale.
- Profile link and picturehttps://www.facebook.com/100064735918245; https://platform-lookaside.fbsbx.com/platform/profilepic/?asid=100064735918245&height=50&width=50&ext=1752600000&hash=AbZ9xY2mN7qP
This links the uploaded record back to the friend's Facebook profile and image.
The code reads the fb-ar response header as JSON and forwards fb-ar.access_token to the background script.
- Cookie
- <redacted>
The code parses the JSON response and converts each returned friend object into a profile record for upload.
- Authorization
- Bearer <redacted>
The code expects a JSON response with status="success" and data entries whose user values are sent back to the tab.
- Content-Type
- application/json
- Authorization
- Bearer <redacted>
Token retrieval, Graph API query, and Novalya upload in shipped code
Readable token extraction flow
content_script/facebook/facebook_friend_list.js$(function () { if (window.location.href.endsWith("#get_fb_access_token") || window.location.href.endsWith("#/get_fb_access_token")) { let attempts = 0; const timer = setInterval(async function () { attempts++; if (attempts < 5) { try { const response = await fetch("https://www.facebook.com/x/oauth/status?client_id=124024574287414&input_token&origin=1&redirect_uri=https%3A%2F%2Fwww.instagram.com%2Faccounts%2Fsignup%2F&sdk=joey&wants_cookie_data=true", { credentials: "include" }); if (response.ok) { const header = JSON.parse(response.headers.get("fb-ar") || "{}"); if (header.access_token) { clearInterval(timer); chrome.runtime.sendMessage({ action: "fb_access_token_ready", token: header.access_token }); } } } catch (error) { console.error("Fetch failed:", error); } } else { clearInterval(timer); chrome.runtime.sendMessage({ action: "fb_access_token_ready", token: null }); } }, 1000); }});Readable background token resolver
background/facebook_friend_list_bg.jsconst pendingFbTokenResolvers = new Map();chrome.runtime.onMessage.addListener((message, sender) => { if (!message || message.action !== "fb_access_token_ready") return; if (!sender.tab) return; const resolve = pendingFbTokenResolvers.get(sender.tab.id); if (resolve) { pendingFbTokenResolvers.delete(sender.tab.id); try { chrome.tabs.remove(sender.tab.id); } catch (error) {} resolve(message.token || null); }});const getFBFriendsAccessToken = () => new Promise((resolve) => { chrome.tabs.create({ url: "https://www.instagram.com/#get_fb_access_token", active: false, pinned: true }, (tab) => { if (!chrome.runtime.lastError && tab) { pendingFbTokenResolvers.set(tab.id, resolve); setTimeout(() => { if (pendingFbTokenResolvers.has(tab.id)) { pendingFbTokenResolvers.delete(tab.id); try { chrome.tabs.remove(tab.id); } catch (error) {} resolve(null); } }, 15000); } else { resolve(null); } }); });Readable Graph API request branch
background/facebook_friend_list_bg.jsif (message.action === "startFbFriendsDetailSyncing") { if (fbBasicSyncingAborted) return false; const token = message.token; const ids = message.ids.join(","); const tabId = sender.tab.id; fetch(`https://graph.facebook.com/?ids=${ids}&fields=id,age_range,birthday,email,mobile_phone,gender,hometown,languages,link,locale,location,name,picture.type(normal)&format=json&suppress_http_code=1`, { method: "GET", headers: { Authorization: `Bearer ${token}` }, redirect: "follow" }) .then((response) => response.json().then((data) => ({ status: response.status, data }))) .then(({ status, data }) => { if (status === 200) { prepareProfileDataToSave(data, tabId); } else { console.error("FB friend detail batch failed", { status, data }); try { chrome.tabs.sendMessage(tabId, { action: "fbDetailBatchFailed", status, error: data && data.error || null }); } catch (error) {} } }) .catch((error) => { console.error("FB friend detail batch network error", error); try { chrome.tabs.sendMessage(tabId, { action: "fbDetailBatchFailed", status: 0, error: String(error && error.message ? error.message : error) }); } catch (sendError) {} });}Readable profile mapping and upload
background/facebook_friend_list_bg.jsconst prepareProfileDataToSave = (graphResponse, tabId) => { const profiles = []; Object.keys(graphResponse).forEach((id) => { const friend = graphResponse[id]; const profile = { id: friend.id, name: friend.name, birthday: friend.birthday || null, gender: friend.gender || null, hometown: friend.hometown?.name || null, location: friend.location?.name || null, languages: friend.languages ? friend.languages.map((language) => language.name).join(", ") : null, locale: friend.locale || null, email: friend.email || null, phone: friend.mobile_phone || null, age: friend.age_range ? friend.age_range.min !== undefined && friend.age_range.max !== undefined ? `${friend.age_range.min}-${friend.age_range.max}` : friend.age_range.min !== undefined ? `min ${friend.age_range.min}` : null : null }; profiles.push(profile); }); saveFbProfileData(profiles, tabId);};const saveFbProfileData = (profiles, tabId) => { const headers = new Headers(); headers.append("Content-Type", "application/json"); headers.append("Authorization", "Bearer " + authToken); const request = { method: "POST", headers, body: JSON.stringify(profiles), redirect: "follow" }; fetch(insta_base_api_url + "/novadata/api/sync-fb-friends-details", request) .then((response) => response.json()) .then((responseBody) => { if (responseBody.status === "success") { const users = responseBody.data.map((entry) => entry.user); chrome.tabs.sendMessage(tabId, { action: "fbFriendsDetailSyncingDone", newlyAdded: profiles.length, users }); } }) .catch((error) => console.log(error));};- www.facebook.com
Receives the OAuth status request made with the signed-in browser cookies.
- graph.facebook.com
Receives bearer-token Graph API requests for selected friend profile fields.
- api-v2.novalya.com
Receives the assembled Facebook friend profile records through Novalya's sync-fb-friends-details API.
- www.instagram.com
Opened by the extension as the page context that triggers the Facebook token retrieval flow.
Where it sends data
Destinations our analysis observed Novalya 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.
- graph.facebook.com
Novalya sends data to graph.facebook.com. 3 other extensions we have analysed send data here.
- api-v2.novalya.com
Novalya sends data to api-v2.novalya.com. No other extension we have analysed sends data here.