Is Shopee Save - Download Product Images & Video safe?

High risk

Shopee Save is high risk. Opening a Shopee product page has the extension POST your Shopee origin and product path to imgvidcfig.com/api/shopy, which returns a redirect URL. Your tab briefly loads imgvidcfig.com with that URL in the query string, then returns.…

E-Mediav3.0.21Chrome Web Store
79Risk
Who publishes it

E-Media - 1 other listing from the same operator, none carrying a finding

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

Publisher
E-Media

Same store account

1 other listing published from this account, 5k+ users between them, none of them carrying a finding.

Shared hosts - 2 hostnames

Hostnames hardcoded in this extension that few other listings call. That can mean one operator behind both, and it can equally mean a small shared vendor, so it is context rather than a conclusion. Hosts that many listings call are left out: they are services, not connections.

alisaveplus.com
Also called by 1 other listing: AliSave Plus
imgvidcfig.com
Also called by 4 other listings, including AliSave Plus, Ali Quick - AliExpress Dropship Tool

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

Publishers can request a review.

Findings

Shopee Product Pages Routed Through imgvidcfig.com Before Loading

Opening a Shopee product page has the extension POST your Shopee origin and product path to imgvidcfig.com/api/shopy, which returns a redirect URL.

Your tab briefly loads imgvidcfig.com with that URL in the query string, then returns.

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

You open any Shopee product page.

The extension did this

The extension sends the product URL path and your Shopee origin to imgvidcfig.com before the page loads.

Your browser tab is redirected through an imgvidcfig.com URL containing the encoded original product URL, then bounced back to Shopee, all within milliseconds.

Captured request
POSThttps://imgvidcfig.com/api/shopy

HTTP 200. Response headers include keepurl: https://imgvidcfig.com/r?ikuc=%2Fshopee.com%2Fproduct-i.111.222

Headers
uuid
/CANARY_BIRD_12345_product-i.111.222
source
https://shopee.com
content-type
application/json
What imgvidcfig.com receives for each Shopee product visit
  • Product page path
    /CANARY_BIRD_12345_product-i.111.222

    The exact Shopee product path you navigated to, identifying which product you viewed.

  • Your Shopee regional domain
    https://shopee.com

    Which Shopee country site you use (e.g. shopee.com, shopee.sg, shopee.vn).

The code that does this

The interception and redirect code, from the extension's shipping source.

Readable version

POST to imgvidcfig.com (the _e function)

const pendingRedirects = {};   // ye: pathname → redirect URL mapconst visitedPaths = [];       // ve: dedup guardconst API_BASE = 'https://imgvidcfig.com/api';  // geasync function notifyServer(productPath, shopeeOrigin) {  // Skip if we've already logged this path  if (Object.keys(pendingRedirects).includes(productPath)) return;  const endpoint = `${API_BASE}/shopy`;  const response = await axios({    method: 'POST',    url: endpoint,    headers: {      'content-type': 'application/json',      source: shopeeOrigin,   // e.g. 'https://shopee.com'      uuid: productPath       // e.g. '/product-i.111.222'    }  });  const keepUrl = response.headers.get('keepurl');  if (keepUrl) {    // Store redirect URL keyed by product path    Object.assign(pendingRedirects, { [productPath]: keepUrl });  }}

Navigation interception + redirect

chrome.webRequest.onBeforeRequest.addListener((event) => {  if (isShopeeProductUrl(event.url)) {    const path = new URL(event.url).pathname;    // Trigger the server POST (once per path, with 15s cooldown)    if (!visitedPaths.includes(path)) {      visitedPaths.push(path);      notifyServer(path, new URL(event.url).origin);      setTimeout(() => {        visitedPaths.splice(visitedPaths.indexOf(path), 1);      }, 15000);    }    // If a redirect URL came back from the server, redirect the tab through it    const pendingPaths = Object.keys(pendingRedirects);    if (pendingPaths.length > 0) {      const path = pendingPaths[0];      const redirectUrl = new URL(pendingRedirects[path]);      const currentUrl = new URL(event.url);      // Embed the original Shopee URL in the redirect      redirectUrl.searchParams.set('ikuc', encodeURIComponent(currentUrl.href));      delete pendingRedirects[path];      chrome.tabs.update(event.tabId, { url: redirectUrl.href });    }  }}, { urls: ['<all_urls>'], types: ['main_frame'] });

Return bounce: redirects back to Shopee once imgvidcfig.com responds

chrome.webRequest.onHeadersReceived.addListener((event) => {  const ikuc = new URL(event.url).searchParams.get('ikuc');  if (ikuc) {    // The tab is currently on imgvidcfig.com — bounce it back to the original Shopee URL    const originalUrl = new URL(decodeURIComponent(ikuc));    chrome.tabs.update(event.tabId, { url: originalUrl.href });  }}, { urls: ['<all_urls>'], types: ['main_frame'] });
Where your product browsing data is sent
    • imgvidcfig.com

    Receives a POST for every Shopee product page opened. Ownership not publicly disclosed; domain registration is privacy-protected.

Remote server writes arbitrary keys into Shopee Save's extension storage

Opening a Shopee product page has the background script query imgvidcfig.com and copy the response's `prefix` field into local storage, unfiltered.

One response set keys like `__POSITION_DISPLAY`, then a content script read that value back.

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

You open a Shopee product page.

Any shopee.* domain with a product-ID path pattern in the URL triggers this.

The extension did this

The extension asks imgvidcfig.com for configuration, then writes the server's answer directly into its own local storage.

No allowlist restricts which keys the response is allowed to set.

Captured request
POSThttps://imgvidcfig.com/api/shopy

HTTP 200. Body: {"data":{"prefix":{"__POSITION_DISPLAY":".page-product.container","__SELECTOR_TITLE":"._44qnta span"...}}}, observed during dynamic analysis on a live Shopee product-page visit.

Headers
uuid
/123456789/1046680173
source
https://shopee.co.th
content-type
application/json
The code that does this

background.js, the POST and the unchecked storage write

Readable version

_e() — POSTs to imgvidcfig.com, then writes the response's prefix object to storage unchecked

assets/js/background.js:5705-5742
  function _e() {    return (_e = o()(i.a.mark((function e(t, n) {      var r, a, o, s, u, l;      return i.a.wrap((function(e) {        for (;;) switch (e.prev = e.next) {          case 0:            if (e.prev = 0, !Object.keys(ye).find((function(e) {                return e === t              }))) {              e.next = 5;              break            }            return e.abrupt("return");          case 5:            return o = "".concat(ge, "/shopy"), e.next = 8, h()({              method: "POST",              url: o,              adapter: w,              headers: {                "content-type": "application/json",                source: n,                uuid: t              }            });          case 8:            s = e.sent, u = null == s || null === (r = s.headers) || void 0 === r ? void 0 : r.get("keepurl"), l = (null === (a = s.data) || void 0 === a ? void 0 : a.prefix) || {}, u && (D(c()({}, T, u)), Object.assign(ye, c()({}, t, u))), D(me({}, l)), e.next = 17;            break;          case 15:            e.prev = 15, e.t0 = e.catch(0);          case 17:          case "end":            return e.stop()        }      }), e, null, [        [0, 15]      ])    })))).apply(this, arguments)  }

D() / M() / me() — D(me({}, prefix)) resolves to a bare chrome.storage.local.set(prefix) call

assets/js/background.js
function D(e) {  return M.apply(this, arguments)}function M() {  return (M = o()(i.a.mark((function e(t) {    return i.a.wrap((function(e) {      for (;;) switch (e.prev = e.next) {        case 0:          return e.abrupt("return", new Promise((function(e) {            chrome.storage.local.set(t, (function() {              return e(!0)            }))          })));        case 1:        case "end":          return e.stop()      }    }), e)  })))).apply(this, arguments)}function me(e) {  for (var t = 1; t < arguments.length; t++) {    var n = null != arguments[t] ? arguments[t] : {};    t % 2 ? pe(Object(n), !0).forEach((function(t) {      c()(e, t, n[t])    })) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(n)) : pe(Object(n)).forEach((function(t) {      Object.defineProperty(e, t, Object.getOwnPropertyDescriptor(n, t))    }))  }  return e}
Server-supplied keys observed landing in chrome.storage.local
  • Page container selector
    __POSITION_DISPLAY: ".page-product .container"

    A CSS selector, supplied by the server rather than shipped in code, used to locate the product image/title block.

  • Product title selector
    __SELECTOR_TITLE: "._44qnta span"

    A CSS selector for the product title element, likewise sent by imgvidcfig.com rather than hardcoded in the extension.

Where the configuration channel is hosted
    • imgvidcfig.com

    Receives a POST on every Shopee product-page visit and returns a 'prefix' object that the extension writes into its own local storage unchecked. Not a Shopee or Chrome domain.

Content Scripts Injected Into Every Website, Not Just Shopee

The extension's stated job is downloading Shopee images and videos, but its manifest runs content scripts on every site. vendors.js and content_scripts.js inject everywhere at document_end, reaching banking, email, social sites.

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

You visit any website, including banking sites, email, and social media.

The extension did this

The extension's content scripts are injected into the page and gain read/write access to its DOM, form inputs, and page content.

This occurs on every HTTP/HTTPS page, not only on Shopee. The extension's stated purpose is Shopee image/video downloading, which does not require access to non-Shopee pages.

The code that does this

The content_scripts declaration in manifest.json vs. what a Shopee-only scope would look like.

Readable version

What a Shopee-scoped content_scripts block would look like

// A content_scripts block scoped to Shopee only:"content_scripts": [  {    "matches": [      "*://*.shopee.com/*",      "*://*.shopee.sg/*",      "*://*.shopee.vn/*",      "*://*.shopee.co.id/*",      "*://*.shopee.ph/*",      "*://*.shopee.com.my/*",      "*://*.shopee.co.th/*",      "*://*.shopee.tw/*"    ],    "js": [      "assets/js/vendors.js",      "assets/js/content_scripts.js"    ],    "css": [      "assets/css/app.css"    ],    "run_at": "document_end"  }]// The actual extension uses '<all_urls>' instead, covering every website.
What DOM access on every page means for your data
  • Form inputs on any site
    Password field on your bank's login page

    Content scripts can read text typed into any form field, including login pages, banking forms, and search boxes, on any website you visit.

  • Full page DOM
    Email inbox content on Gmail, account numbers on banking site

    The injected scripts can read and modify the entire HTML structure of any page, including content not visible to the user.

  • Page URL and navigation context
    https://mail.google.com/mail/u/0/#inbox

    Content scripts run after the page loads and have access to the current URL and document location on every site.

Scope vs. observed behavior

The broad injection scope grants capability, not confirmed abuse. Our dynamic analysis confirmed content scripts load on non-Shopee origins (vendor and content script resources were observed loading with source='cs' on shopee.com, and extension-related DOM elements were observed on Google). We did not observe the extension reading or transmitting page content from non-Shopee sites in our test sessions. However, the code has the DOM access to do so on every page you visit.

What it can do

Permissions this extension asks for, as declared in version 3.0.21. Asking for a permission is not a finding on its own - it is what the extension can do if it chooses to.

  • Read and change your data on every site you visit

    <all_urls>

  • Store data in your browser

    storage

  • Watch every request your browser makes

    webRequest

  • Start, monitor and manage your downloads

    downloads

  • Add items to the right-click menu

    contextMenus

Updated 30 September 2026likamafaejphcadlcclbnjddckicmblo