Is Hover Zoom+ safe?

Medium risk

HoverZoom is medium risk. On any Google domain, an injected script replaces XHR.prototype.open. Every XHR response feeds one sessionStorage entry, hoverZoomXHROpenData, unfiltered, so autocomplete, account, and search responses accumulate. Active on google.com.…

HoverZoom+v1.1.17Chrome Web Store
52Risk
Who publishes it

HoverZoom+ - 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
HoverZoom+

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

Publishers can request a review.

Findings

All XHR Response Bodies Accumulated in sessionStorage on Google Domains

On any Google domain, an injected script replaces XHR.prototype.open.

Every XHR response feeds one sessionStorage entry, hoverZoomXHROpenData, unfiltered, so autocomplete, account, and search responses accumulate.

Active on google.com.

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

You visit any Google domain (google.com, google.co.uk, and roughly 150 other ccTLDs).

The hook runs on page load; no explicit user action is needed beyond navigation.

The extension did this

The extension replaces XMLHttpRequest.prototype.open so that every XHR response body is appended to sessionStorage['hoverZoomXHROpenData'] before any other page code can read it.

There is no URL filter, all XHR responses on the page, including account API calls, accumulate in the same storage key until the tab closes.

The code that does this

The XHR hook script injected into every Google page (js/hoverZoomXHROpen.js):

Readable version
// Injected as a <script> tag into every google.* page.// Replaces XMLHttpRequest.prototype.open — affects ALL XHR on the page,// not just extension-initiated ones.if (typeof oldXHROpen !== 'function') {  // guard: hook only once per tab    oldXHROpen = window.XMLHttpRequest.prototype.open;  // save original    window.XMLHttpRequest.prototype.open = function(method, url, async, user, password) {        // Register a listener before the request starts:        this.addEventListener('load', function() {            try {                // No URL check — captures ALL XHR responses:                let existing = sessionStorage.getItem('hoverZoomXHROpenData');                if (existing == null) {                    sessionStorage.setItem('hoverZoomXHROpenData', this.responseText);                } else {                    // Concatenate indefinitely; only resets if storage is full:                    sessionStorage.setItem('hoverZoomXHROpenData', existing + this.responseText);                }            } catch {}        });        // Proceed with the original open() call:        return oldXHROpen.apply(this, arguments);    };}
What's stored on your device

XHR responses for one Google session: autocomplete, search snippets, account JSON. Grows until the 5MB cap, then only the latest is kept.

Location
sessionStorage key 'hoverZoomXHROpenData' (set in the page's own storage by the injected script)
Contents
{"app":{"client_id":"chrome_ext"},"features":{"suggest":true,"images":true},"images":[{"id":"BPbcgkAKO1TMCM","url":"https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcT...","source":"https://example.com/photo.jpg","w":800,"h":600}]}{"items":[{"id":"autocomplete-0","content":"weather today"}]}

Vinted Listing API Responses Intercepted and Stored in sessionStorage

On Vinted, an injected script replaces fetch and XHR.open.

Responses from /api/v2/catalog/items and /api/v2/homepage/ (photos/blocks key) are stored as hzVintedApiData.

Active on vinted.com; feeds the zoom plugin via a CustomEvent bridge.

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

You browse the Vinted catalog or homepage.

The hook activates on any vinted.com page where the extension's content script runs.

The extension did this

The extension replaces window.fetch and XMLHttpRequest.prototype.open so that responses from Vinted's listing API paths are stored in sessionStorage['hzVintedApiData'].

Responses are captured if they come from /api/v2/catalog/items or /api/v2/homepage/ and contain the string '"photos"' or '"blocks"' in the response body.

The code that does this

The fetch and XHR intercept hooks in hoverZoomVintedFetch.js (shipped source):

Readable version

Annotated: both fetch and XHR are replaced to capture Vinted API responses

// Both window.fetch and XMLHttpRequest.prototype.open are replaced// so the extension can intercept responses before Vinted's own JS processes them.// Filter: only catalog and homepage API paths are intercepted:function shouldIntercept(url) {    return url.includes('/api/v2/catalog/items') || url.includes('/api/v2/homepage/');}// Storage helper: overwrites (does not append) sessionStorage on each intercepted response:function store(text) {    sessionStorage.setItem('hzVintedApiData', text);  // latest response only    document.dispatchEvent(new CustomEvent('hzVintedApiData'));  // notify content script}// Fetch hook — response is cloned so Vinted's own .then() still resolves:window.fetch = function(...args) {    const url = (typeof args[0] === 'string') ? args[0] : (args[0] && args[0].url) || '';    const promise = originalFetch.apply(this, args);    if (shouldIntercept(url)) {        promise.then(resp => {            resp.clone().text().then(text => {                if (text.includes('"photos"') || text.includes('"blocks"')) {                    store(text);  // captured: Vinted listing API payload                }            });        });    }    return promise;  // original promise returned unmodified};
What's stored on your device

Last Vinted listing: item details, seller IDs, login names, prices, photo URLs, read by the zoom plugin, overwritten each response.

Location
sessionStorage key 'hzVintedApiData' on vinted.com
Contents
{  "items": [    {      "id": 74821903,      "user_id": 12847291,      "title": "Levi's 501 Jeans W30 L32",      "price": "15.00",      "currency": "EUR",      "photos": [        {          "id": 291847302,          "full_size_url": "https://images1.vinted.net/a1/2ba5a/1713445291,f800,eec5,6e52e,5d45f.jpeg",          "url": "https://images1.vinted.net/tc/2ba5a/1713445291,f800,eec5,6e52e,5d45f.jpeg",          "thumbnails": [            {              "type": "thumb310x430",              "url": "https://images1.vinted.net/tc/thumb310x430/2ba5a.jpeg"            }          ]        }      ],      "user": {        "id": 12847291,        "login": "fashionresale_fr",        "profile_url": "https://www.vinted.fr/member/12847291-fashionresale-fr"      }    }  ],  "pagination": {    "current_page": 1,    "total_pages": 5,    "total_entries": 82  }}

Hovering Vinted Homepage Listings Triggers Authenticated Wardrobe API Crawl

Hovering a Vinted homepage listing (URL has homepage_session_id) sends paginated GETs to /api/v2/wardrobe/{sellerId}/items with the session cookie, fetching each hovered seller's wardrobe. 5 requests seen after 5 hovers, 12-383 KB each.

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

You hover your mouse over a listing on the Vinted homepage feed.

Only homepage feed items trigger this path, the extension checks that the item's href contains 'homepage_session_id=' (vinted.js:161).

The extension did this

The extension sends paginated GET requests to /api/v2/wardrobe/{sellerId}/items with your Vinted session cookies, fetching all pages of the seller's inventory.

Requests use credentials:'include', so Vinted's server receives them as authenticated calls under your session. The fetched data is cached locally for the zoom gallery.

Captured request
GEThttps://www.vinted.com/api/v2/wardrobe/12847291/items?page=1&per_page=20&order=newest_first

HTTP 200, JSON payload containing the seller's wardrobe items: seller user ID, login name, item IDs, titles, prices (EUR), photo URLs, and pagination metadata ({total_pages: N, total_entries: M}). Response size ranged from 12 KB to 383 KB in observed requests.

Headers
Accept
application/json, text/plain, */*,image/webp
Cookie
v_uid=12847291; _vinted_fr_session=<redacted>
Cache-Control
no-store
The code that does this

Wardrobe crawl: hover detect, URL build, authenticated fetch (vinted.js + hoverZoomVintedFetch.js)

Readable version

Annotated end-to-end flow

// STEP 1 — vinted.js detects a homepage feed item on hover:var useWardrobeApi = itemHref.includes('homepage_session_id=') || location.pathname === '/';// homepage_session_id= only appears on items from the Vinted homepage feed.// STEP 2 — buildWardrobeApiUrl() constructs the paginated endpoint:function buildWardrobeApiUrl(userId, page) {    // wardrobePageSize = 20 (hard-coded)    return '/api/v2/wardrobe/' + userId + '/items?page=' + page + '&per_page=20&order=newest_first';}// STEP 3 — crawlWardrobe() paginates until complete:function crawlWardrobe(userId) {    // Sends hzVintedWardrobeRequest CustomEvent to the page-world script.    // Page-world script issues the actual fetch with session cookies:    _origFetch.call(window, wardrobeUrl, {        credentials: 'include',  // <-- attaches the user's Vinted session cookies        cache: 'no-store',        headers: { Accept: 'application/json, text/plain, */*,image/webp' }    });    // Response is stored in plug._wardrobeCatalogs[userId] for the zoom gallery.    // Crawl continues to the next page until state.complete === true.}
Fields present in the wardrobe API response for each listed item
  • Seller user ID
    12847291

    The numeric Vinted user ID of the seller whose wardrobe was fetched.

  • Seller login name
    fashionresale_fr

    The seller's visible Vinted username.

  • Item listing URLs
    https://www.vinted.fr/items/74821903-levis-501-jeans

    Full URLs to every item in the seller's wardrobe, not just the one you hovered over.

  • Item prices
    15.00 EUR

    Price and currency for each listed item.

  • Photo URLs
    https://images1.vinted.net/a1/2ba5a/1713445291,f800.jpeg

    Full-size and thumbnail photo URLs for every item in the wardrobe.

  • Wardrobe size
    82 items across 5 pages

    Total number of items the seller has listed (via pagination metadata).

Updated 30 September 2026pccckmaobkjjboncdfnnofkonhgpceea