Is كوبونات صفقة | Safqa Coupons safe?
Safqa Coupons is medium risk. With access to all sites, the extension checks the URL of every page you visit, not just shopping sites, against a regex list from api.joinsafqa.com. Matching URLs triggered the extension; a non-matching site (Wikipedia) did not.…
Who publishes itAhmed Abdelsalam - 1 other listing from the same operator, none carrying a finding
Ahmed Abdelsalam - 1 other listing 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
1 other listing published from this account, 1k+ users between them, none of them carrying a finding.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Every Site You Visit Is Screened Against a Server-Supplied List
With access to all sites, the extension checks the URL of every page you visit, not just shopping sites, against a regex list from api.joinsafqa.com.
Matching URLs triggered the extension; a non-matching site (Wikipedia) did not.
- Severity
- Medium unwanted
- Type
- Unexpected
- CWE
- CWE-200
- Source
- Dynamic sandbox
You navigate to any website in any tab, not only shopping sites.
The background service worker checks the full page URL against every regex pattern in a server-supplied list to decide whether to act.
This check runs on every completed navigation, using the extension's access to all sites.
Tab-navigation listener and regex match in background.js
Tab-update listener, readable
background.js:9454static handleTabUpdates() { chrome.tabs.onUpdated.addListener(async (tabId, changeInfo, tab) => { if (!changeInfo.url || !tabId) return; if (changeInfo.status === 'complete' && changeInfo.url) { tabStore.getState().updateTabUrl(tabId, changeInfo.url); // records the URL runShopMatch(changeInfo.url, tabId); // regex-matches it await Handlers.updateUserAuth(); } });}Regex match against the full site list, readable
background.js:9338findMatchingShop() { // this.shops is the full list fetched from api.joinsafqa.com/v1/shop/app-state return this.shops.find(shop => new RegExp(shop.regex_shop ?? '').exec(this.tabUrl) !== null);}Observed during dynamic analysis: a 200 response carrying a 334-entry list of shop objects, each with a regex_shop URL-match pattern. This is the list every visited URL is checked against.
- X-joinsafqa
- joinsafqa-5.0.3
immediately on page load
You didNavigate to a product page on amazon.ae, noon.com, or aliexpress.com (all present in the fetched shop list).
The extension didA price-tracking analytics event fires carrying the visited URL, and for two of the three visits a same-tab overlay opens.
n/a, no match
You didNavigate to a Wikipedia article (absent from the fetched shop list).
The extension didNo analytics event and no overlay; the extension takes no further action.
- api.joinsafqa.com
Developer's API server. Supplies the regex list every visited URL is checked against, and can add or remove monitored sites without an extension update.
Remote Server Controls Which Sites Are Scraped and What Data Is Extracted
The extension downloads its monitored-site list, URL patterns, and scraping selectors from api.joinsafqa.com at startup and every 15 min.
GET /shop/app-state observed at startup twice; config controlled scraping, updatable without notice.
- Severity
- Medium unwanted
- Type
- Unexpected
- CWE
- CWE-829
- Source
- Dynamic sandbox
You install the extension or your browser starts.
The extension fetches its full list of monitored sites, URL patterns, and CSS scraping selectors from the developer's server.
The server can change this configuration at any time, adding new sites or selectors, without issuing an extension update. Changes take effect within 15 minutes.
Every 15 minutes
The extension refreshes its shop configuration from api.joinsafqa.com every 15 minutes via a chrome.alarms alarm. Any change to monitored sites or scraping selectors takes effect on the next refresh cycle.
Our dynamic analysis observed this request at startup in both test sessions (run IDs 389 and 392, status 200). The response contains an array of shop objects each with: regex_shop (URL matching), regex_cart, regex_product, tracker_price_selector, tracker_title_selector, tracker_image_selector, tracker_enabled, and priceHistory_enabled. In our sessions, amazon.ae and noon.ae were returned as active monitored shops, and visiting their product pages triggered scraping.
- X-joinsafqa
- joinsafqa-5.0.3
Remote config loading and 15-minute refresh alarm in background.js
loadShopping — readable
background.js:8222loadShopping: async () => { const response = await api.shop.getAppState(); // GET /shop/app-state if (response.success) { store.set({ shopping: response.data }); // applies new config immediately return response.data; } // fall back to cached config if request fails const cached = store.getState().shopping; return cached.shops.length > 0 ? cached : { categories: [], shops: [], coupons: [], adV1: [], cartarms: [] };}setupShoppingUpdateAlarm — readable
background.js:8267static setupShoppingUpdateAlarm() { // SHOPPING_UPDATE = 15 (minutes) chrome.alarms.create('updateShopping', { periodInMinutes: 15 }); chrome.alarms.onAlarm.addListener((alarm) => { if (alarm.name === 'updateShopping') { store.getState().loadShopping(); // re-fetch config from server } });}- URL match pattern (regex_shop)amazon\.ae
A regular expression the extension tests every URL against to decide if you are on a monitored site.
- Price selector (tracker_price_selector).a-price-whole
A CSS selector used to extract the displayed price from the page HTML. The server decides what to read.
- Title selector (tracker_title_selector)#productTitle
A CSS selector used to extract the product title from the page HTML.
- Scraping enabled (tracker_enabled)true
A boolean flag the server sets per-shop. When true, background HTML fetching and data posting are activated for that site.
- api.joinsafqa.com
Developer's API server. Serves the shop config (site list, regex, CSS selectors) controlling monitored pages, and receives all collected product data via /product/ssearch.
Product Page Scraping via Server-Controlled CSS Selectors
Visiting a product page on a supported site makes the extension fetch the page HTML and extract data via server CSS selectors, sending the URL, a site ID (SSIN), and price to api.joinsafqa.com.
Confirmed: 6 POSTs on amazon.ae and noon.com.
- Severity
- Medium unwanted
- Type
- Unexpected
- CWE
- CWE-200
- Source
- Dynamic sandbox
You visit a product page on a supported shopping site.
The extension fetches the full HTML of that page in the background and sends the product identifier and URL to its server.
Which sites are monitored is determined by regex patterns downloaded from api.joinsafqa.com; the server can add or remove sites without an extension update.
Our dynamic analysis captured 6 POST requests to this endpoint during visits to amazon.ae and noon.com product pages. The 'd' field is AES-encrypted using a hardcoded key (see claim 6046). Decrypted content: {"ssin":"B09G9HD6PD","url":"https://www.amazon.ae/dp/B09G9HD6PD/","shopId":"amazon.ae","currentPrice":null}
- X-joinsafqa
- joinsafqa-5.0.3
- Content-Type
- application/json
{ "d": "U2FsdGVkX1+PZ1Ehp3Z7xmK2qLF8nT3oVcBwYpRdJeA9s6iHnMkZuQvWbCfXtGy1"}- Product page URLhttps://www.amazon.ae/dp/B09G9HD6PD/
The full URL of the product page you are viewing, sent to the developer's server.
- Product identifier (SSIN)B09G9HD6PD
A site-specific product code extracted from the URL, used by the server to look up price history.
- Shop identifieramazon.ae
Which shopping site you are on.
- Current pricenull
The price displayed on the page, extracted using a server-supplied CSS selector.
HTML fetch and server-controlled scraping in background.js
fetchProductDetails — readable form
background.js:17611static async fetchProductDetails(pageUrl, shopConfig) { return fetch(pageUrl) .then(resp => resp.text()) // downloads full HTML .then(html => { const { document } = parseHTML(html); const result = {}; // All selectors come from the server-provided shop config if (shopConfig.tracker_price_selector) { const el = document.querySelector(shopConfig.tracker_price_selector); if (el) { let val = shopConfig.tracker_price_attribute ? el.getAttribute(shopConfig.tracker_price_attribute) : el.textContent; if (val && shopConfig.tracker_price_transform === 'parseFloat') val = parseFloat(val.replace(/[^\d.]/g, '')); result.price = val; } } // ... title and image extracted similarly ... return Object.keys(result).length > 0 ? result : null; });}fetchProductHistory — readable form
background.js:9976async fetchProductHistory(productData) { // productData = { ssin, offerCode, url, shopId, currentPrice } const encrypted = new EncryptionService().encrypt(JSON.stringify(productData)); const response = await api.product.ssearch({ d: encrypted }); return response.success ? response.data?.prices ?? [] : [];}- api.joinsafqa.com
Developer's API server. Receives encrypted product identifiers (URL, SSIN, price) per visit, and serves shop config (site list + CSS selectors) controlling monitored pages.