Is EHunt - Etsy™ Rank Tool safe?

Medium risk

EHunt is medium risk. EHunt's Etsy review analysis collects reviews and buyer context from the page and Etsy APIs. Background posts the array (text, name/URL, transaction ID, rating, images) to esale.io. Not triggered in dynamic analysis; no body captured.

TouhouProjIncv2.3.9Chrome Web Store
47Risk
Who publishes it

TouhouProjInc - 2 other listings from the same operator, 2 of them carrying a finding

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

Publisher
TouhouProjInc

Same store account

2 other listings published from this account, 70k+ users between them. 2 of them carry a finding.

Shared hosts - 1 hostname

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.

ehunt.ai
Also called by 4 other listings, including GPT for Ecom: Product Listing optimizer, ShopifyHunt - Shopify™ Spy Tool & AliExpress™ Product Research, Easy Exporter - Etsy™ order exporter

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

Publishers can request a review.

Findings

Etsy review details sent to esale.io for AI analysis

EHunt's Etsy review analysis collects reviews and buyer context from the page and Etsy APIs.

Background posts the array (text, name/URL, transaction ID, rating, images) to esale.io.

Not triggered in dynamic analysis; no body captured.

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

You start EHunt's review analysis feature on an Etsy listing or shop page.

The code path checks that enough reviews are available before asking for analysis.

The extension did this

The extension gathers Etsy review records and sends the review array to esale.io.

The request is tied to the current shop or listing and includes the parsed reviews count.

Review fields assembled before the POST
  • Review text
    Great quality and shipped fast (illustrative)

    Shows what a buyer wrote about a product or shop and is sent with the analysis request.

  • Transaction ID
    3289107541 (illustrative)

    Links a review record to a specific Etsy transaction when Etsy exposes that value on the page or API response.

  • Buyer name
    Avery Stone (illustrative)

    Can identify the Etsy buyer associated with the review when a name is present.

  • Buyer profile URL
    https://www.etsy.com/people/averyhandmade (illustrative)

    Connects the review to a specific Etsy profile page when the profile link is present.

  • Rating, image, and date
    5 stars, 2026-06-18, https://i.etsystatic.com/12345678/r/il/example.jpg (illustrative)

    Adds context about the review contents and timing that travels with the review record.

Captured request
POSThttps://esale.io/api/reviews/e-analyse?2.3.5

No POST body was captured because the review-analysis path was not triggered during dynamic analysis.

Headers
Content-Type
application/json
The code that does this

Review parsing and the esale.io analysis request

Readable version

Readable review parsers and Etsy review fetchers

js/content.js
Rl = e => {  const t = [];  return e.forEach(e => {    const a = e.querySelector("p.prose")?.textContent.trim() || "";    let l = "";    const s = e.querySelector("[data-transaction-id]");    l = s ? s.dataset.transactionId : "";    const o = e.querySelector(".shop2-review-attribution");    let n = "",      i = "";    if (o) {      const e = o.querySelector("a");      e && (n = e.textContent.trim(), i = e.href)    }    const r = e.querySelector('input[name="rating"]'),      c = r ? parseInt(r.value) : 0,      u = "";    let d = "",      p = "";    o && (d = o.textContent.replace(n, "").trim(), d && (p = Math.floor(Date.parse(d) / 1e3)));    const _ = i && i.match(/\/people\/([^/?]+)/)?.[1] || "";    t.push({      content: a,      transaction_id: l,      auther: n,      auther_url: i,      rateing: c,      images: u,      date: p,      auther_name: _    })  }), t}, Sl = e => {  const t = [];  return e.forEach(e => {    const a = e.reviewContent?.reviewText || "",      l = e.transactionId,      s = e.buyerInfo?.name || "",      o = e.buyerInfo?.profileUrl || "",      n = e.reviewInfo?.rating || 0,      i = e.reviewContent?.appreciationPhotoUrl || "",      r = e.reviewInfo?.reviewDate || "",      c = r ? Math.floor(Date.parse(r) / 1e3) : "",      u = e.buyerInfo?.profileUrl && e.buyerInfo.profileUrl.match(/\/people\/([^/?]+)/)?.[1] || "";    t.push({      content: a,      transaction_id: l,      auther: s,      auther_url: o,      rateing: n,      images: i,      date: c,      auther_name: u    })  }), t}, Fl = async (e, t, a = 1) => {  const l = {      log_performance_metrics: !0,      specs: {        deep_dive_reviews: ["Etsy\\Modules\\ListingPage\\Reviews\\DeepDive\\AsyncApiSpec", {          listing_id: parseInt(e),          shop_id: parseInt(t),          scope: "listingReviews",          page: a,          sort_option: "Relevancy",          rating_filter: null,          tag_filters: [],          should_lazy_load_images: !1,          should_show_variations: !0        }]      }    },    s = await fetch("https://www.etsy.com/api/v3/ajax/bespoke/member/neu/specs/deep_dive_reviews", {      method: "POST",      mode: "cors",      headers: {        "Content-Type": "application/json",        "x-csrf-token": Il(),        accept: "*/*",        origin: "https://www.etsy.com"      },      referrerPolicy: "strict-origin-when-cross-origin",      body: JSON.stringify(l)    });  return await s.json()}, Pl = async (e, t = 1) => {  const a = new URLSearchParams({      log_performance_metrics: !1,      "specs[shop-reviews][]": "Shop2_ApiSpecs_Reviews",      "specs[shop-reviews][1][shopname]": e,      "specs[shop-reviews][1][page]": t,      "specs[shop-reviews][1][reviews_per_page]": 10,      "specs[shop-reviews][1][should_hide_reviews]": !0,      "specs[shop-reviews][1][is_in_shop_home]": !0,      "specs[shop-reviews][1][sort_option]": "Relevancy",      runtime_analysis: !1    }),    l = await fetch(`https://www.etsy.com/api/v3/ajax/bespoke/public/neu/specs/shop-reviews?${a.toString()}`, {      method: "GET",      mode: "cors",      headers: {        accept: "*/*",        "x-detected-locale": "VND|en-US|VN",        "x-etsy-protection": "1"      },      referrerPolicy: "strict-origin-when-cross-origin"    });  return await l.json()}

Readable extension message carrying reviews

js/content.js
Z = async (e, t, a, l) => new Promise(s => {  chrome.runtime.sendMessage({    option: "review_analysis_ask",    shop_id: e,    listing_id: t,    reviews_num: a,    reviews: l,    is_shop: "shop_detail" == D.value  }, e => s(e))})

Readable background handler posting to esale.io

js/background.js
review_analysis_ask: () => {  chrome.storage.local.get("token", e => {    const t = {      "Content-Type": "application/json"    };    e.token && (t["AUTHORIZATION"] = e.token), fetch(`${o}/api/reviews/e-analyse?${s}`, {      method: "POST",      body: JSON.stringify({        shop_id: a.shop_id,        listing_id: a.listing_id,        reviews_num: a.reviews_num,        type: 1,        is_shop: a.is_shop || 0,        reviews: a.reviews,        version: +s.replaceAll(".", "")      }),      headers: t    }).then(e => e.json()).then(function(e) {      return c(e), !0    }).catch(() => (_(), c(), !0))  })}
Hosts involved in the review-analysis path
    • www.etsy.com

    Source of listing and shop review data that the content script fetches and parses.

    • esale.io

    Receives the assembled review-analysis request from the background script.

What it can do

Permissions this extension asks for, as declared in version 2.3.6. Asking for a permission is not a finding on its own - it is what the extension can do if it chooses to. The current listed version is 2.3.9, which we have not unpacked yet.

  • Read and change your data on etsyhunt.com

    *://*.etsyhunt.com/*

  • Schedule its own background tasks

    alarms

  • See the address and title of every tab you have open

    tabs

  • Read and change cookies, including the ones that keep you signed in

    cookies

  • Store data in your browser

    storage

Updated 30 September 2026pmpgnefoilpinnblccjddomajohmbpko