Is Easy Package Tracker safe?

High risk

Easy Package Tracker reports the URL and title of every page you visit to its own server, tagged with a persistent install ID.

A background script fires on every completed page load across all sites, sending your browsing history to easypackagetrack.com tied to an ID minted for your install and reused on every request. On YouTube it goes further, fetching the video page itself and scraping its metadata into the same reports. It also reads that install ID back out to any webpage or embedded iframe that asks for it, and can silently open new tabs to a server-chosen destination after you click an ordinary link.

Easy Package Trackerv1.0.2Chrome Web Store
81Risk

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

Publishers can request a review.

Findings

Easy Package Tracker tags every page load with one install-wide ID

Code analysis shows the background reports the URL and title of every page you load to a vendor ad endpoint, tagged with one ID minted at install and reused every time, linking your browsing across sites to one identifier.

Severity
Critical unwanted
Type
Unexpected
CWE
CWE-359
Source
Static analysis
What actually happens
You did this

You finish loading any ordinary website in your browser.

This fires for essentially any http(s) page, not only shopping or shipping sites.

The extension did this

The background service worker sends that page's full URL and title to a vendor ad server.

The report is tagged with an ID unique to your install, minted once and reused every time, so every report is linkable to the same install.

The code that does this

Every completed page load is reported with the same install-wide ID

Readable version

onUpdated listener, renamed

serviceWorker.js:676-686
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {  (async () => {    if (changeInfo.status !== "complete") return;    await handleWidgetTimeout(tabId);    await handleWidgetLoad(tabId, tab);    if (!tab || !tab.title || !tab.url) return;    if (pageScopeTabs.has(tabId)) return;    if (isYoutubeHost(tab.url)) return;    await reportPage(tab);  })();});

reportPage + getOrMintUserId, renamed

serviceWorker.js:290-331 (reportPage), 168-181 (getOrMintUserId)
async function getOrMintUserId() {  let id = await readCachedUserId();  if (id) return id;  const res = await fetch(`${API_BASE}/install.php`, { method: "POST" });  const json = await res.json().catch(() => null);  id = String(json.userId);  await cacheUserId(id);  return id;}async function reportPage(tab, context) {  try {    const ctx = context && typeof context === "object" ? context : {};    const url = typeof ctx.pageUrl === "string" && ctx.pageUrl.trim() !== "" ? ctx.pageUrl : tab && tab.url ? tab.url : "";    const title = typeof ctx.pageTitle === "string" ? ctx.pageTitle : tab && tab.title ? tab.title : "";    if (!tab || !tab.id || !url) return;    if (!(await widgetEnabled())) return;    const keywords = ctx.description ? String(ctx.description).trim().substring(0, 5000) : "";    const userId = await getOrMintUserId();    const body = { userId, url, title, keywords };    const res = await fetch(`${API_BASE}/bid.php`, {      method: "POST",      headers: { "Content-Type": "application/json" },      body: JSON.stringify(body)    });    const reply = await res.json().catch(() => ({}));    await chrome.storage.local.set({ [tab.id]: reply });  } catch {}}
Fields sent to easypackagetrack.com on every completed page load
  • Page URL
    https://www.example-retailer.com/product/wireless-mouse

    The exact address of the page you just finished loading.

  • Page Title
    Wireless Mouse - Example Retailer

    The page's <title> text, sent alongside the URL.

  • Install ID
    8214736950182746301

    A number assigned once when the extension was installed, then reused on every report so all your visits link to it.

Where the reports go
    • easypackagetrack.com

    The vendor's own ad-serving backend for this extension. Mints the install ID via install.php and receives every page-load report via bid.php.

Check if you're affected

Reads the extension's cached per-install ID directly from its own storage, confirming it is a single value reused across reports.

Requires
  • Chrome with the extension installed
  • Developer mode enabled in chrome://extensions
read-persistent-user-id.js · js
// Run from the extension's own service worker console:// chrome://extensions -> enable Developer mode -> click the// "service worker" link under Easy Package Tracker -> Console tab.chrome.storage.local.get("userId", (result) => {  console.log("Persistent install ID reused on every page-load report:", result.userId);});
How to run it
  1. 1Open chrome://extensions, enable Developer mode.
  2. 2Click "service worker" under Easy Package Tracker.
  3. 3Run this in the Console tab.
  4. 4Re-run after visiting other sites; the ID stays the same.
Observation

Static analysis finding. This behaviour was identified by reading the shipped extension code and has not yet been reproduced in a live run. The trigger conditions and the exact data sent are read from the code, not from an observed capture.

A server-chosen instruction can open a new tab on your next ordinary link click

Code analysis shows that when 'Smart Flow' is enabled (the default), any ordinary link click on any page can trigger a brand-new tab to a server-chosen URL, which can be pinned or muted and later auto-focused, with no confirmation prompt.

Severity
High unwanted
Type
Unexpected
CWE
CWE-506
Source
Static analysis
What actually happens
You did this

You left-click any ordinary link on any website, unrelated to package tracking.

The content script's click listener fires for any unmodified click on a real http(s) anchor, on every page it runs on.

The extension did this

If a pending instruction exists for that tab, a brand-new tab opens to a URL the server chose, with no confirmation prompt.

The new tab can be opened pinned and muted, and alarms scheduled from the same instruction can later unpin or auto-focus it.

The code that does this

A link click anywhere can open a server-chosen tab when Smart Flow is on

Readable version

click listener, renamed

package-widget.js:81-96
function isPlainLinkClick(event) {  try {    let node = event.target;    while (node && node.nodeName && node.nodeName.toLowerCase() !== "a" && node.parentNode instanceof Node) {      node = node.parentNode;    }    if (node && node.nodeName && node.nodeName.toLowerCase() === "a") {      const href = node.getAttribute("href");      const target = node.getAttribute("target");      const middleClick = event.button === 1;      const isUrl = isValidUrl(href);      return !(target || !isUrl || middleClick);    }    return false;  } catch { return false; }}function attachClickListener() {  if (!document.body) return;  document.body.addEventListener("click", (event) => {    if (!(event.target instanceof Element)) return;    sendMessage({ type: "user-click", data: isPlainLinkClick(event) });  });}

handleUserClick (V), renamed

serviceWorker.js:522-543
async function handleUserClick(tabId, isPlainLink, tab) {  if (!(await smartFlowEnabled())) { await clearPendingKey(tabId, "nta"); return; }  const pending = await getTabRecord(tabId);  if (!pending) return;  const nta = pending.nta;  if (!nta) return;  const targetLink = nta.targetLink;  if (!targetLink) return;  if (!(await ntaCooldownElapsed())) return;  const url = `${targetLink}${isPlainLink ? "&l=1" : ""}`;  const newTab = await chrome.tabs.create({ url, active: !nta.delayed, pinned: (nta.pinned || 0) > 0 });  if (newTab && newTab.id) {    await recordOpenedTab(newTab.id, { createdAt: Date.now(), openedUrl: url, sourceTabId: tabId });    await markLastOpenTime();    if (nta.muted) chrome.tabs.update(newTab.id, { muted: true });    if ((nta.pinned || 0) > 0) chrome.alarms.create(`unpin-tab-${newTab.id}`, { when: Date.now() + nta.pinned * 1000 });    if ((nta.delayed || 0) > 0) chrome.alarms.create(`focus-tab-${newTab.id}`, { when: Date.now() + nta.delayed * 1000 });  }  await clearPendingKey(tabId, "nta");}
Fields in the server-supplied 'nta' instruction that controls the new tab
  • Target Link
    https://partner-offers.example.net/landing?ref=ept

    The URL the new tab navigates to. Nothing in the shipped code constrains or previews this value.

  • Pinned (seconds)
    30

    If set, the new tab opens pinned and an alarm unpins it after this many seconds.

  • Delayed (seconds)
    10

    If set, the tab opens in the background and an alarm brings it to the foreground after this delay.

  • Muted
    true

    If true, the new tab's audio is muted immediately after it opens.

Where the tab-opening instruction comes from
    • easypackagetrack.com

    Same vendor endpoint used for page reports; its bid.php responses can include the 'nta' object consumed by the click handler.

Reproduce it yourself

Seeds the exact record shape a server response writes for a tab, then one ordinary link click in that tab opens the seeded URL, proving the code path is real and reachable.

Requires
  • Chrome with the extension installed
  • Developer mode enabled in chrome://extensions
seed-and-trigger-redirect.js · js
// Run from the extension's own service worker console:// chrome://extensions -> enable Developer mode -> click the// "service worker" link under Easy Package Tracker.//// Seeds the same record shape a bid.php response would write for the// CURRENT active tab, then any ordinary link click in that tab// triggers the extension's own handleUserClick() path.const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });await chrome.storage.local.set({  [String(tab.id)]: {    nta: {      targetLink: "https://example.com/redirect-poc",      pinned: 30,      muted: true,      delayed: 10    }  }});console.log(`Seeded a pending redirect for tab ${tab.id}. Now click any plain link on that tab's page.`);
How to run it
  1. 1Open chrome://extensions, enable Developer mode.
  2. 2Click "service worker" under Easy Package Tracker, run this there.
  3. 3Click any plain link on the active tab's page.
  4. 4A new tab opens, pinned and muted.
Observation

Static analysis finding. This behaviour was identified by reading the shipped extension code and has not yet been reproduced in a live run. The trigger conditions and the exact data sent are read from the code, not from an observed capture.

Opening a YouTube video makes it re-fetch the page and report scraped metadata

Code analysis shows the content script re-fetches the YouTube video page you opened, scrapes meta-tag data from it, and reports that as keywords to the same vendor ad endpoint, tagged with your install-wide ID.

Severity
High unwanted
Type
Unexpected
CWE
CWE-359
Source
Static analysis
What actually happens
You did this

You open a youtube.com/watch or /shorts video, including via YouTube's own in-page navigation.

The content script watches the page for this path pattern and reacts even without a full page reload.

The extension did this

The content script fetches that same page's HTML a second time and scrapes meta-tag video metadata out of it.

The scraped description is then reported to a vendor ad server as 'keywords', tagged with the same install-wide ID used for ordinary page reports.

The code that does this

The content script fetches the video page itself and scrapes off-screen meta tags

Readable version
package-widget.js:113-133, renamed
function onNavigate() {  const onYoutube = isYoutubeHost(window.location.href);  sendMessage({ type: "page-scope", data: onYoutube });  if (!onYoutube) {    sendMessage({ type: "page-probe", data: { pageUrl: window.location.href, pageTitle: document.title || "" } });    return;  }  const titleEl = document.querySelector("title");  const checkPath = debounce((currentUrl) => {    if (!isWatchOrShortsPath(currentUrl)) return;    // The content script fetches the current video page's HTML itself,    // separate from the browser's own navigation.    fetch(currentUrl)      .then((res) => res.text())      .then((html) => {        const scraped = {};        const doc = new DOMParser().parseFromString(html, "text/html");        Array.from(doc.getElementsByTagName("meta")).forEach((meta) => {          const key = meta.getAttribute("itemprop");          if (key && ["name", "description", "genre"].includes(key)) {            scraped[key] = meta.getAttribute("content");          }        });        sendMessage({          type: "page-context",          data: Object.assign({ pageUrl: currentUrl, pageTitle: document.title || "" }, scraped)        });      });  }, 200);  const observer = new MutationObserver(() => checkPath(document.location.href));  if (titleEl) observer.observe(titleEl, { childList: true });  checkPath(document.location.href);}
Fields reported to bid.php for each YouTube video opened
  • Video URL
    https://www.youtube.com/watch?v=dQw4w9WgXcQ

    The watch or shorts URL you opened.

  • Keywords
    Official trailer for the upcoming documentary series, produced by Example Studios.

    The video's own description meta tag, scraped by the content script's extra fetch, truncated to 5000 characters.

  • Install ID
    8214736950182746301

    The same install-wide ID used to report your ordinary page visits.

Where the scraped metadata goes
    • easypackagetrack.com

    Same endpoint and install ID used for the extension's general page-load reports; bid.php receives the video keywords here too.

Reproduce it yourself

Reproduces the content script's own meta-tag scrape on a YouTube watch page, showing exactly what it would report as keywords.

Requires
  • A youtube.com/watch or /shorts page open
extract-youtube-meta.js · js
// Run in the DevTools console on any youtube.com/watch?v=... or// /shorts/... page. Reproduces package-widget.js's own re-fetch and// meta scrape (itemprop name/description/genre) without needing the// extension installed.fetch(location.href)  .then((res) => res.text())  .then((html) => {    const doc = new DOMParser().parseFromString(html, "text/html");    const wanted = ["name", "description", "genre"];    const scraped = {};    doc.querySelectorAll("meta[itemprop]").forEach((meta) => {      const key = meta.getAttribute("itemprop");      if (wanted.includes(key)) scraped[key] = meta.getAttribute("content");    });    console.log("Fields the extension would report as 'keywords':", scraped);  });
How to run it
  1. 1Open any youtube.com/watch?v=... or /shorts/... video.
  2. 2Open DevTools and switch to the Console.
  3. 3Paste and run this script.
  4. 4The printed object matches what package-widget.js scrapes and sends to bid.php as 'keywords'.
Observation

Static analysis finding. This behaviour was identified by reading the shipped extension code and has not yet been reproduced in a live run. The trigger conditions and the exact data sent are read from the code, not from an observed capture.

Where it sends data

Destinations our analysis observed Easy Package Tracker 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.

  • easypackagetrack.com

    Easy Package Tracker sends data to easypackagetrack.com. No other extension we have analysed sends data here.

Updated 30 September 2026acpdphchanikhccpmmljgbmcckpakbpe