Is Flipshope: Price Tracker and much more safe?

High risk

Flipshope is high risk. On a supported product or search page, e.g. Amazon or Flipkart, Flipshope reads CSS selectors from a config at data.flipshope.com, pulls title, price, image URLs, and page URL, and uploads that plus a per-install ID to data.flipshope.com.…

Ashutosh Goyalv4.3.6Chrome Web Store
77Risk
Who publishes it

Technosense Media Pvt. Ltd - no other listings under this identity

What this publisher told the store about itself, and the other listings that told it the same thing.

Publisher
Ashutosh Goyal
Declared legal entity
Technosense Media Pvt. Ltd
Registered address
111/14, Second Floor, Kumbha Marg, sector 11, Pratap Nagar, Jaipur, Rajasthan 302033, IN

AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.

Publishers can request a review.

Findings

Product pages and search results scraped to data.flipshope.com

On a supported product or search page, e.g.

Amazon or Flipkart, Flipshope reads CSS selectors from a config at data.flipshope.com, pulls title, price, image URLs, and page URL, and uploads that plus a per-install ID to data.flipshope.com.

Severity
High unwanted
Type
Unexpected
CWE
CWE-200
Source
Dynamic sandbox
What actually happens
You did this

You open a product page or a search-results page on a shopping site that matches Flipshope's remote store configuration, such as Amazon or Flipkart.

The page would normally load with no extra network activity beyond the store's own site.

The extension did this

The content script scrapes the product title, price, image URLs, and page URL from the page using selectors it downloaded from data.flipshope.com, then uploads them.

A single product page is uploaded to data.flipshope.com/api/products/; a listing page with multiple products (e.g. search results) is uploaded to data.flipshope.com/api/graph/, both tagged with a per-install identifier.

Fields included in each upload
  • Product page URL
    https://www.amazon.in/dp/B082LZGK39/

    Identifies exactly which product or listing page you were viewing.

  • Product ID
    B082LZGK39

    The store's internal identifier for the exact item on the page.

  • Product title
    Ambrane Unbreakable 60W Fast Charging Type-C Cable

    The full name of the product or listing you viewed.

  • Price
    ₹109

    The price shown on the page at the time you viewed it.

  • Product image URLs
    https://m.media-amazon.com/images/I/71ExampleABC.jpg

    Links to every product image shown on the page.

  • Per-install identifier (extid)
    A7kP2qLs

    Lets data.flipshope.com tie every upload back to your specific browser install over time.

The code that does this

Background handler that uploads scraped page data

Readable version

sendpairs / sendprods message handlers

background.bundle.js
case "sendpairs":  p.pairs && ((extid, site, pairs) => {    he(u + "graph/", { store: site, data: pairs, extid, v: 2 }, 2, 0);  })(r.myid, p.site, p.pairs);  break;case "sendprods":  p.prod_data && ((extid, site, prodData) => {    he(u + "products/", { store: site, data: prodData, extid, v: 2 }, 2, 0);  })(r.myid, p.site, p.prod_data);  break;

Content script loads remote selectors and applies them

contentScript.bundle.js
chrome.storage.local.get("graph_base", async (stored) => {  let config = stored && stored.graph_base;  if (!(currentSite === "shopsy.in" || (config && config[currentSite]))) return;  async function applySelectors() {    let siteEntries = (config && config[currentSite]) || null;    let match = siteEntries      ? siteEntries.find((entry) => new RegExp(entry.must_path).test(window.location.href))      : null;    if (!match && currentSite === "shopsy.in") {      match = { site_id: 17, must_path: ".*", pg_data_sel: "[]", prod_data_sel: "{}" };    }    if (match) {      activeConfig = match;      try {        let listingSelectors = JSON.parse(activeConfig.pg_data_sel);        scrapeListingFields(activeConfig.site_id, listingSelectors, 0);      } catch (e) {}      try {        window.pd_sels = activeConfig.prod_data_sel;        pd_sels = JSON.parse(activeConfig.prod_data_sel);        let isTracked = await isProductTracked(activeConfig.site_id, window.location.href);        if (isTracked) {          renderTrackingUI(activeConfig, isTracked);          setTimeout(() => scrapeProductFields(activeConfig.site_id, pd_sels, 0), 500);        }      } catch (e) {}    }  }  applySelectors();});
Captured request
POSThttps://data.flipshope.com/api/products/
Body
{  "store": 2,  "data": {    "pid": "B082LZGK39",    "price": 109,    "title": "Ambrane Unbreakable 60W Fast Charging Type-C Cable (illustrative — the evidence log recorded this field as a truncated summary, not the literal captured string)",    "all_imgurls": [      "(illustrative — an image-URL array was present in the captured body; the evidence log did not record each URL individually)"    ]  },  "extid": "A7kP2qLs",  "v": 2}
Where the scraped data goes
    • data.flipshope.com

    Flipshope's own backend. Receives scraped product/listing data via POST to /api/products/ and /api/graph/, and serves the CSS selector config via /api/getsiteaffdata.

Selector list is remote and can change without an update

The CSS selectors that decide which page elements get scraped are fetched from data.flipshope.com's remote config rather than shipped fixed in the extension. That means the set of fields collected, or the set of sites covered, can change server-side without a new extension version and without further code review of what gets scraped.

Store clicks route through thebestdeals.app first

Clicking an outbound store link on a site in Flipshope's affiliate config blocks the click, sending the browser to thebestdeals.app/api/d first.

The URL includes the destination in dl and may include the stored extid before forwarding.

Severity
Medium unwanted
Type
Unexpected
CWE
CWE-506
Source
Dynamic sandbox
What actually happens
You did this

You click a product or store link on a shopping site that is active in Flipshope's affiliate configuration.

The page would normally follow the anchor's href directly.

The extension did this

The extension stops the normal click and routes the browser through thebestdeals.app/api/d first.

The original href is encoded into the dl query parameter before navigation.

Fields added to the redirect URL
  • Clicked destination
    https://www.amazon.in/dp/B0C6HSSDZK?ref=sr_1_1

    Shows the remote endpoint the exact link you tried to open.

  • Extension instance ID
    A7kP2qLs

    Lets the endpoint group the click with the same browser profile when this value is present.

  • Inventory label
    ext

    Marks the redirect as coming from the extension channel.

  • Source label
    ext_product_page

    Marks the click path as a product-page redirect from the extension.

Captured request
GEThttps://thebestdeals.app/api/d?dl=https%3A%2F%2Fwww.amazon.in%2Fdp%2FB0C6HSSDZK%3Fref%3Dsr_1_1&extid=A7kP2qLs&inventory=ext&source=ext_product_page
The code that does this

The click handler that replaces the destination URL

Readable version

Content script click interception

contentScript.bundle.js
(async () => {  try {    (await pd({ dataMSg: { msg: "check_if_attach_clickEvent", data: window.location.href } }))?.active &&      function () {        try {          async function e(t) {            let n, i, a = null;            try {              if (t.defaultPrevented || t.button !== 0) return;              if (i = t.target.closest("a"), !i || !i.href) return;              t.preventDefault();              const o = new URL(i.href);              n = o.href;              if (i.target === "_blank") a = window.open("", "_blank");              const s = await async function (e) {                try {                  const t = e;                  let n = Es(t);                  const i = await Hs("get", "validClickLinks") || {};                  if (!i?.[n] || !i[n].active) return 0;                  let a = await Hs("get", "graph_base") || {};                  if (!a[n]) return 1;                  let o = a[n]?.site_id;                  return await Ts(parseInt(o), t) ? 1 : 0;                } catch (e) {                  return 0;                }              }(o.href);              const r = Es(o.href), l = await Hs("get", "linkActivationTime") || {}, c = l[r], d = !c || +new Date > c;              if (s && d) {                const e = "https://thebestdeals.app/api/d?dl=", t = await Hs("get", "extid"), n = e + encodeURIComponent(o.href) + (t ? "&extid=" + t : "") + "&inventory=ext&source=ext_product_page";                !function (e, t, n) {                  try {                    e === "_blank" ? n && !n.closed ? (n.location.href = t, n.focus()) : window.open(t, "_blank") : window.location.href = t;                  } catch (e) {}                }(i.target, n, a);                l[r] = +new Date + 18e5;                Hs("set", { linkActivationTime: l });              } else {                a && !a.closed ? (a.location.href = o.href, a.focus()) : (document.removeEventListener("click", e, true), i.click(), setTimeout(() => { document.addEventListener("click", e, true); }, 0));              }            } catch (t) {              a && !a.closed && n ? (a.location.href = n, a.focus()) : i ? (document.removeEventListener("click", e, true), i.click(), setTimeout(() => { document.addEventListener("click", e, true); }, 0)) : n && (window.location.href = n);            }          }          document.addEventListener("click", e, true);        } catch (e) {}      }();  } catch (e) {}})();

Background active-store configuration

background.bundle.js
async function Z() {  const e = function () {    const e = navigator.userAgent || "";    return e.includes("Edg/") ? "edge" : e.includes("OPR/") || e.includes("Opera") ? "opera" : e.includes("Firefox/") ? "firefox" : e.includes("Chrome/") ? "chrome" : "unknown";  }();  K[e] || (K[e] = {});  const a = K[e], t = `https://data.flipshope.com/api/getsiteaffdata?browser=${e}`;  let n = await X(a, t, x, `${p}${e}`), o = {};  n.data.forEach((e => {    o[e.store_name.toLowerCase()] = { active: e.active };  }));  return o;}
When this fires

Every 30 minutes

After one redirected click for a store, the extension records a 30-minute cooldown before redirecting another click for that store.

Remote hosts involved in the click routing
    • thebestdeals.app

    Receives the redirected click URL with dl, optional extid, inventory, and source query parameters.

    • data.flipshope.com

    Provides the active affiliate-store list used to decide whether the click listener should be attached.

Flight search itineraries sent to Flipshope with a persistent device ID

On booking sites like MakeMyTrip, Goibibo, Cleartrip, ixigo, Flipshope reads origin, destination, dates, passengers, cabin class from the URL, sends the itinerary and a per-install ID to data.flipshope.com; Cleartrip triggered two POSTs.

Severity
Medium unwanted
Type
Unexpected
CWE
CWE-200
Source
Dynamic sandbox
What actually happens
You did this

You search for a flight on a supported booking site.

e.g. a Cleartrip results page with origin, destination, dates, and passenger counts in the URL.

The extension did this

The extension parses that itinerary out of the URL and sends it to its own server, tagged with your persistent extension ID.

This happens automatically on page load, with no click or prompt from you.

Captured request
POSThttps://data.flipshope.com/api/flight_fare

Observed during dynamic analysis: navigating to a Cleartrip flight-results URL (DEL to BOM) triggered two POSTs matching this body exactly.

Headers
Content-Type
application/json
Body
{  "from": "DEL",  "to": "BOM",  "date": {    "day": 15,    "month": 9,    "year": 2026  },  "returnDate": {    "day": null,    "month": null,    "year": null  },  "adult": 1,  "cabinClass": "CABIN_CLASS_ECONOMY",  "extensionId": "eXbk89DZ"}
Fields in the itinerary payload
  • Origin airport
    DEL

    Identifies where you're flying from.

  • Destination airport
    BOM

    Identifies where you're flying to.

  • Travel dates
    2026-09-15

    Reveals your planned departure (and return, if round-trip) dates.

  • Passenger count
    1 adult

    Shows how many adults, children, and infants are on the booking.

  • Cabin class
    CABIN_CLASS_ECONOMY

    Shows whether you're searching economy, premium, business, or first class.

  • Persistent extension ID
    eXbk89DZ

    A per-install ID stored as a cookie that ties this and every future search back to the same install.

The code that does this

Parsing a Cleartrip flight-results URL and posting the itinerary

Readable version

Readable equivalent

// case for the URL scheme resolved server-side as "lmcleotkxx" (a Cleartrip-style booking URL)case "lmcleotkxx": {  const from = url.match(/from=([^&]+)/)?.[1] ?? null;  const to = url.match(/to=([^&]+)/)?.[1] ?? null;  const departDate = decodeURIComponent(url.match(/depart_date=([^&]+)/)?.[1] ?? "");  const [day, month, year] = departDate.split("/");  const adults = parseInt(url.match(/adults=([^&]+)/)?.[1] ?? "0", 10);  const cabinClassCode = url.match(/class=([^&]+)/)?.[1] ?? null;  if (!(from && to && day && month && year)) return false;  // ... maps cabinClassCode to CABIN_CLASS_ECONOMY / _BUSINESS / _FIRST ...}// after parsing, in the caller:itinerary.extensionId = await getPersistentExtensionId();const response = await fetch("https://data.flipshope.com/api/flight_fare", {  method: "POST",  headers: { "Content-Type": "application/json" },  body: JSON.stringify(itinerary)});
Where the itinerary goes
    • data.flipshope.com

    Flipshope's own backend. Receives the parsed itinerary and persistent extension ID, and returns fare comparison data shown back to you in the extension UI.

Updated 30 September 2026adikhbfjdbjkhelbdnffogkobkekkkej