Is Handz - פשוט להשוות מחירים safe?

Medium risk

Handz scrapes DOM data on every website and collects yad2 listing data via injected scripts, forwarding results to its own servers.

The extension runs a content script on all pages that fetches a remote configuration from check.zololo.co.il and, for supported sites, extracts DOM and React fiber data using server-defined selectors and posts it back to a server-controlled endpoint. A background crawler periodically injects into open yad2.co.il tabs and fetches vehicle and property listing JSON from yad2 APIs, forwarding the collected data to api.handz.co.il. The extension also tracks yad2 browsing activity with a persistent user identifier sent to Mixpanel.

Handzv8.3.6Chrome Web Store
52Risk

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

Publishers can request a review.

Findings

Background worker scrapes yad2 listings via your open tab, sends to handz

MediumunwantedCWE-506Dynamic sandbox

With a yad2.co.il tab open, a periodic crawler (schedule/URLs from a remote server) finds open tabs, prevents discard, and injects a script fetching yad2's listing API.

What actually happens

You did this

You leave a yad2.co.il tab open in the browser.

No further interaction is needed; the crawler runs on a background timer.

The extension did this

The extension scrapes yad2 listing data through that tab and uploads it.

It injects a fetch of a yad2 listing API into the page, then POSTs the results to api.handz.co.il.

When this fires

Every 1 minute

A setInterval loop checks every minute and, once the server-supplied crawlIntervalMilis has elapsed, injects the next crawl. The interval value is read from the remote config on each cycle and the loop restarts if it changes.

The code that does this

The crawl loop: keep the tab alive, inject a fetch into the page, upload the results

Readable version

The interval loop that injects the crawl and uploads (background.js)

deobfuscated/background.js:8075-8122
const C = setInterval(async () => {  const O = await le();                                  // current remote config  const A = await chrome.tabs.query({ url: "https://*.yad2.co.il/*" });  A.filter(t => t.autoDiscardable).forEach(t =>           // keep tabs from being unloaded    chrome.tabs.update(t.id, { autoDiscardable: false }));  const l = A.filter(t => !t.discarded);  if (l.length && l[0].id) {    chrome.scripting.executeScript({      target: { tabId: l[0].id }, world: "MAIN",      args: [O.crawlPages[w]],                            // server-supplied URL      func: async m => (await (await fetch(m)).json()).data    }, m => {      const x = m[0].result;      const d = [...x.solo, ...x.private, ...x.commercial, ...x.platinum];      io({ entities: d, endpoint: "https://api.handz.co.il/v2.0/entities/vehicles/automated" });    });  }}, 60000);

Captured request

POSThttps://api.handz.co.il/v2.0/entities/vehicles/automated

Captured during dynamic analysis: a single ~60 KB POST carrying 40 full yad2 vehicle-listing objects (price, agency name, manufacturer/model, image URLs, order ids) plus hash/token fields. Values shown are illustrative of the captured structure.

Headers
X-ext-name
handz
Content-Type
application/json
X-ext-version
8.2.0
Body
{"listings":[{"order_id":"a-1234567","price":89000,"customer":{"agency_name":"A.S Motors"},"address":{"city":"Tel Aviv"},"search_fields":{"manufacturer":"Toyota","model":"Corolla"},"meta_data":{"images":["https://img.yad2.co.il/Pic/202506/01/abc123.jpg"]}}, /* ...39 more listing objects... */ ],"token":"<persistent_token>","f":"<sha512 fingerprint>","s":"...","n":"..."}

Fields present in each uploaded listing record

  • Order / listing ID
    a-1234567

    Identifier of the specific yad2 listing.

  • Price
    89000

    Asking price of the listing.

  • Agency name
    A.S Motors

    The seller / agency associated with the listing.

  • Manufacturer / model
    Toyota / Corolla

    Vehicle make and model from the listing's search fields.

  • Listing image URLs
    https://img.yad2.co.il/Pic/202506/01/abc123.jpg

    Links to the listing's photos on the yad2 CDN.

Remote endpoints involved in the crawl

    • extconfig-v3.handz.co.il

    Returns the crawl configuration, the list of yad2 API URLs to crawl (crawlPages) and the interval (crawlIntervalMilis), letting the operator change crawl targets server-side.

    • api.handz.co.il

    Receives the scraped yad2 listing records via POST to /v2.0/entities/vehicles/automated.

Check if you're affected

Surfaces the crawler in a managed fleet by hunting proxy/SIEM logs for the remote crawl-config fetch and the listing-upload endpoint, and lists which managed devices contacted them.

Requires
  • bash
  • grep, awk
  • Access to proxy/SIEM HTTP logs
detect_handz_crawl.sh · sh
#!/usr/bin/env bash# Hunt web-proxy / SIEM access logs for the handz crawl config + upload endpoints.# Usage: ./detect_handz_crawl.sh /path/to/proxy-access.logset -euo pipefailLOG="${1:?usage: detect_handz_crawl.sh <proxy-access-log>}"echo '== crawl-config fetches (extconfig-v3.handz.co.il) =='grep -E 'extconfig-v3\.handz\.co\.il' "$LOG" || echo '  none'echo '== listing uploads (api.handz.co.il/v2.0/entities) =='grep -E 'api\.handz\.co\.il/v2\.0/entities' "$LOG" || echo '  none'echo '== distinct client IPs touching either host =='grep -E 'handz\.co\.il' "$LOG" | awk '{print $1}' | sort -u
How to run it
  1. 1Export web-proxy or SIEM HTTP access logs covering the period of interest.
  2. 2Run ./detect_handz_crawl.sh <access-log>.
  3. 3Review the listed client IPs/devices and correlate to managed assets running the extension.

Every yad2.co.il page you visit is sent to Mixpanel with a persistent ID

MediumunwantedCWE-359Dynamic sandbox

Browsing yad2.co.il, the background worker reports each page you open to Mixpanel: full URL plus a persistent ID reused across sessions, linking browsing to one profile.

What actually happens

You did this

You open a page on yad2.co.il.

For example a property or vehicle listing search such as /realestate/forsale.

The extension did this

The extension reports that page to a third-party analytics service.

It sends the full URL plus a persistent identifier to api.mixpanel.com/track.

What each page-view report contains

  • Full page URL
    https://www.yad2.co.il/realestate/forsale

    The complete address of the yad2 page you opened, including the path.

  • Host
    www.yad2.co.il

    The yad2 hostname for the page.

  • Path and query
    /realestate/forsale ?priceOnly=1

    The specific section and search filters you were viewing.

  • Persistent ID (distinct_id)
    UID-uph880

    A stable identifier stored on the device and reused on every report, linking your visits over time.

  • Mixpanel project token
    8449cd6ac45765b8fa14f5b0377033c3

    The analytics project the data is attributed to.

Captured request

POSThttps://api.mixpanel.com/track

Captured during dynamic analysis after navigating to two yad2 pages; the base64 data= body decodes to an 'ext_page_view' event carrying the full URL and the persistent distinct_id.

Headers
Content-Type
application/x-www-form-urlencoded
Body
data=W3siZXZlbnQiOiJleHRfcGFnZV92aWV3IiwicHJvcGVydGllcyI6eyJmdWxsVXJsIjoiaHR0cHM6Ly93d3cueWFkMi5jby5pbC9yZWFsZXN0YXRlL2ZvcnNhbGUiLCJob3N0Ijoid3d3LnlhZDIuY28uaWwiLCJwYXRoIjoiL3JlYWxlc3RhdGUvZm9yc2FsZSIsInF1ZXJ5IjoiIiwiaGFzaCI6IiIsImVudiI6InByb2R1Y3Rpb24iLCJkaXN0aW5jdF9pZCI6IlVJRC11cGg4ODAiLCJ0b2tlbiI6Ijg0NDljZDZhYzQ1NzY1YjhmYTE0ZjViMDM3NzAzM2MzIn19XQ==

Why you can't catch this in DevTools

The page-view report is base64-encoded as the data= form parameter, so the URL and identifier are not visible in plain text on the wire.

Decoded value
[  {    "event": "ext_page_view",    "properties": {      "fullUrl": "https://www.yad2.co.il/realestate/forsale",      "host": "www.yad2.co.il",      "path": "/realestate/forsale",      "query": "",      "hash": "",      "env": "production",      "distinct_id": "UID-uph880",      "token": "8449cd6ac45765b8fa14f5b0377033c3"    }  }]

The code that does this

The shipped header comment says data goes only to Google Analytics; the code sends it to Mixpanel

Readable version

The navigation listener and the actual destination (background.js)

deobfuscated/background.js:68-145
chrome.tabs.onUpdated.addListener((tabId, w) => {  if (w.url) {    const f = new URL(w.url);    if (f.host.indexOf("yad2.co.il") === -1) return;    as("ext_page_view", { fullUrl: w.url, host: f.host, path: f.pathname, query: f.search, hash: f.hash });  }});async function as(event, props) {  const distinct_id = await _a() || await Ju();      // persistent ID  const body = btoa(JSON.stringify([{ event, properties: { ...props, env: "production", distinct_id, token: "8449cd6ac45765b8fa14f5b0377033c3" } }]));  await fetch("https://api.mixpanel.com/track", { method: "POST", body: `data=${body}` });}

Where the page-view data is sent

    • api.mixpanel.com

    Third-party product-analytics platform; receives the 'ext_page_view' events containing the full yad2 URL and the persistent distinct_id.

Remote config inserts ads into Yad2 listings

MediumunwantedCWE-829Dynamic sandbox

Browsing matching Yad2 pages, the config endpoint returns a popOverAds iframe for extension.zololo.co.il/embed/hf/.

What actually happens

You did this

You browse a matching Yad2 listing page while the extension has a non-empty ad configuration.

The bundled page rules cover real-estate and vehicle listing pages under yad2.co.il.

The extension did this

The extension inserts a configured ad element into the listing page.

The inserted element can be an image, iframe, or video, depending on the remote configuration entry.

Captured request

GEThttps://extconfig-v3.handz.co.il/

Dynamic analysis captured the response returning popOverAds with an iframe source of https://extension.zololo.co.il/embed/hf/.

Remote ad fields that control the page insertion

  • Ad element type
    iframe

    This decides what kind of element is added to the listing page you are viewing.

  • Ad source URL
    https://extension.zololo.co.il/embed/hf/

    This is the external content that the inserted page element loads.

  • Insertion position
    BEFORE

    This controls whether the added element appears before, after, inside, or at the start of the matched listing content.

  • Listing page target
    [data-testid="price"] inside a listing card

    The extension uses Yad2 listing page elements to decide where the configured ad appears.

The code that does this

Config fetch, ad validation, message passing, and DOM insertion

Readable version

Background fetch stores popOverAds from remote config

background.js
async function Qs() {  try {    const t = await fetch("https://extconfig-v3.handz.co.il/");    if (!t.ok) throw new Error(`HTTP error! status: ${t.status}`);    const e = await t.json();    await chrome.storage.local.set({      config_sites_ids: {        ids: e.sites.map(n => n.id),        timestamp: Date.now()      }    });    const r = e.sites.reduce((n, a) => (n[`config_site_${a.id}`] = a, n), {});    return await chrome.storage.local.set(r), await chrome.storage.local.set({      crawlPages: e.crawlPages,      crawlIntervalMilis: e.crawlIntervalMilis,      liSyncRatio: e.liSyncRatio,      zolSyncRatio: e.zolSyncRatio,      popOverAds: e.popOverAds ?? []    }), e  } catch (t) {    throw t  }}

Content script asks for and validates popOverAds

content-scripts/content.js
function oa(i) {  if (typeof i != "string") return !1;  try {    return new URL(i).protocol === "https:"  } catch {    return !1  }}function Ol(i) {  if (!Array.isArray(i)) return [];  const a = [];  for (const r of i) {    if (!r || typeof r != "object") continue;    const o = r;    if (!Pl.includes(o.type) || !oa(o.src)) continue;    const f = {      ...o    };    f.targetUrl !== void 0 && !oa(f.targetUrl) && delete f.targetUrl, a.push(f)  }  return a}async function Ll() {  try {    const i = await chrome.runtime.sendMessage(s0({      type: Tl    }));    return Ol(i)  } catch {    return []  }}

Validated ads are passed into the inserted micro-app

content-scripts/content.js
function fx(i, a, r, o, f, d) {  const _ = {};  return sx(), i.map(c => {    if (c[r] && a[c[r]]) {      const v = a[c[r]].querySelector(o);      if (v) {        const l = Math.random().toString(36).replace(/[^a-z]+/g, "").substring(0, 5),          h = document.createElement(l),          [w, g] = _r(void 0),          m = ax({            entityId: c[r],            syncedServerData: w,            popOverAds: d          }),          y = v.matches('[data-testid="price"]') && v.closest('[class*="item-data-content_priceSlot"]');        switch (f) {          case An.APPEND:            v.append(h);            break;          case An.PREPEND:            y ? v.before(h) : v.prepend(h);            break;          case An.AFTER:            v.after(h);            break;          case An.BEFORE:            v.before(h);            break;          default:            v.append(h)        }        Ea.push(jl(() => m, h)), _[c[r]] = {          microApp: m,          setSyncedServerData: g,          randonContainerElement: h        }      }    }  }), _}

Hosts involved in the observed path

    • extconfig-v3.handz.co.il

    Remote configuration endpoint that returned the popOverAds descriptor.

    • extension.zololo.co.il

    Iframe source URL present in the observed popOverAds entry.

Where it sends data

Destinations our analysis observed Handz - פשוט להשוות מחירים 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.mixpanel.comwidely used

    Handz - פשוט להשוות מחירים sends data to api.mixpanel.com. A widely used service: 38 other extensions we have analyzed send data here.

  • api.handz.co.il

    Handz - פשוט להשוות מחירים sends data to api.handz.co.il. No other extension we have analyzed sends data here.

  • check.zololo.co.il

    Handz - פשוט להשוות מחירים sends data to check.zololo.co.il. No other extension we have analyzed sends data here.

  • extconfig-v3.handz.co.il

    Handz - פשוט להשוות מחירים sends data to extconfig-v3.handz.co.il. No other extension we have analyzed sends data here.

Updated 2 November 2026gjgdhpnmmpcpgjaepjehbijfgdblflbi