Is Handz - פשוט להשוות מחירים safe?
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.
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
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.
One capture: 40 vehicle listings POSTed with price and model data.
What actually happens
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 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
The interval loop that injects the crawl and uploads (background.js)
deobfuscated/background.js:8075-8122const 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
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.
- X-ext-name
- handz
- Content-Type
- application/json
- X-ext-version
- 8.2.0
{"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 IDa-1234567
Identifier of the specific yad2 listing.
- Price89000
Asking price of the listing.
- Agency nameA.S Motors
The seller / agency associated with the listing.
- Manufacturer / modelToyota / Corolla
Vehicle make and model from the listing's search fields.
- Listing image URLshttps://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.
- bash
- grep, awk
- Access to proxy/SIEM HTTP logs
#!/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- 1Export web-proxy or SIEM HTTP access logs covering the period of interest.
- 2Run ./detect_handz_crawl.sh <access-log>.
- 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
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.
Two visits left as POSTs to api.mixpanel.com/track with the ID.
What actually happens
You open a page on yad2.co.il.
For example a property or vehicle listing search such as /realestate/forsale.
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 URLhttps://www.yad2.co.il/realestate/forsale
The complete address of the yad2 page you opened, including the path.
- Hostwww.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 token8449cd6ac45765b8fa14f5b0377033c3
The analytics project the data is attributed to.
Captured request
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.
- Content-Type
- application/x-www-form-urlencoded
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.
[ { "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
The navigation listener and the actual destination (background.js)
deobfuscated/background.js:68-145chrome.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
Browsing matching Yad2 pages, the config endpoint returns a popOverAds iframe for extension.zololo.co.il/embed/hf/.
The content script fetches it, validates the URL, and inserts the image, iframe, or video near listing prices.
What actually happens
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 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
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 typeiframe
This decides what kind of element is added to the listing page you are viewing.
- Ad source URLhttps://extension.zololo.co.il/embed/hf/
This is the external content that the inserted page element loads.
- Insertion positionBEFORE
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
Background fetch stores popOverAds from remote config
background.jsasync 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.jsfunction 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.jsfunction 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.