Is كوبونات صفقة | Safqa Coupons safe?

Medium risk

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.…

Ahmed Abdelsalamv5.0.9Chrome Web Store
52Risk
Who publishes it

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.

Publisher
Ahmed Abdelsalam

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

You navigate to any website in any tab, not only shopping sites.

The extension did this

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.

The code that does this

Tab-navigation listener and regex match in background.js

Readable version

Tab-update listener, readable

background.js:9454
static 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:9338
findMatchingShop() {  // 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);}
Captured request
GEThttps://api.joinsafqa.com/v1/shop/app-state

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.

Headers
X-joinsafqa
joinsafqa-5.0.3
Matching vs. non-matching navigations during dynamic analysis
  1. immediately on page load

    You did

    Navigate to a product page on amazon.ae, noon.com, or aliexpress.com (all present in the fetched shop list).

    The extension did

    A price-tracking analytics event fires carrying the visited URL, and for two of the three visits a same-tab overlay opens.

  2. n/a, no match

    You did

    Navigate to a Wikipedia article (absent from the fetched shop list).

    The extension did

    No analytics event and no overlay; the extension takes no further action.

Where the match list comes from and what it enables
    • 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
What actually happens
You did this

You install the extension or your browser starts.

The extension did this

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.

When this fires

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.

Captured request
GEThttps://api.joinsafqa.com/v1/shop/app-state

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.

Headers
X-joinsafqa
joinsafqa-5.0.3
The code that does this

Remote config loading and 15-minute refresh alarm in background.js

Readable version

loadShopping — readable

background.js:8222
loadShopping: 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:8267
static 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    }  });}
Server-supplied fields in each shop configuration object that control extension behavior
  • 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.

Configuration and data destination
    • 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
What actually happens
You did this

You visit a product page on a supported shopping site.

The extension did this

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.

Captured request
POSThttps://api.joinsafqa.com/v1/product/ssearch

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}

Headers
X-joinsafqa
joinsafqa-5.0.3
Content-Type
application/json
Body
{  "d": "U2FsdGVkX1+PZ1Ehp3Z7xmK2qLF8nT3oVcBwYpRdJeA9s6iHnMkZuQvWbCfXtGy1"}
Data posted to api.joinsafqa.com/v1/product/ssearch on each product page visit
  • Product page URL
    https://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 identifier
    amazon.ae

    Which shopping site you are on.

  • Current price
    null

    The price displayed on the page, extracted using a server-supplied CSS selector.

The code that does this

HTML fetch and server-controlled scraping in background.js

Readable version

fetchProductDetails — readable form

background.js:17611
static 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:9976
async 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 ?? [] : [];}
Where product data is sent
    • 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.

Updated 30 September 2026dkdfaikjbcicjbjejichilcfidbifjdl