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.
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.
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 crawl loop: keep the tab alive, inject a fetch into the page, upload the results
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);| 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":"..."}| Field | Value | Why it matters | |
|---|---|---|---|
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. |
- 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.
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.
#!/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.log
set -euo pipefail
LOG="${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.
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.
| Field | Value | Why it matters | |
|---|---|---|---|
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. |
| Content-Type | application/x-www-form-urlencoded |
data=W3siZXZlbnQiOiJleHRfcGFnZV92aWV3IiwicHJvcGVydGllcyI6eyJmdWxsVXJsIjoiaHR0cHM6Ly93d3cueWFkMi5jby5pbC9yZWFsZXN0YXRlL2ZvcnNhbGUiLCJob3N0Ijoid3d3LnlhZDIuY28uaWwiLCJwYXRoIjoiL3JlYWxlc3RhdGUvZm9yc2FsZSIsInF1ZXJ5IjoiIiwiaGFzaCI6IiIsImVudiI6InByb2R1Y3Rpb24iLCJkaXN0aW5jdF9pZCI6IlVJRC11cGg4ODAiLCJ0b2tlbiI6Ijg0NDljZDZhYzQ1NzY1YjhmYTE0ZjViMDM3NzAzM2MzIn19XQ==
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 shipped header comment says data goes only to Google Analytics; the code sends it to Mixpanel
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}` });
}- 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.
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.
| Field | Value | Why it matters | |
|---|---|---|---|
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. |
Config fetch, ad validation, message passing, and DOM insertion
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
}
}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 []
}
}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
}
}
}
}), _
}- 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.