Is Commanders Act Assistant safe?

Medium risk

Commanders Act Assistant reads cookies and page URLs from every tab every 4 seconds and sends them to privacy.trustcommander.net.

A background timer inspects every open tab, reads every resource URL the page loaded, pulls the cookies tied to those URLs, and queues records with the cookie name, domain, expiry, and the page's full URL. Every 2 seconds it batches and sends this queue to the extension vendor's collection endpoint, and this happens on every website with no toggle to turn it off, regardless of the recorded consent status. Separately, the extension will install a browser-wide redirect that swaps a site's Commanders Act container script for a different version whenever it sees a page carrying specific URL parameters, without checking that the request came from Commanders Act itself.

Commanders Actv3.7.1Chrome Web Store
49Risk

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

Publishers can request a review.

Findings

Reads cookies from every site you visit and uploads them every 2 seconds

Code analysis shows the extension reads cookies and the full URL of every page you visit, on any site, every few seconds, and uploads that data to the developer's own server.

There is no setting to turn this off.

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

You browse to any ordinary website with the extension installed, whether or not its side panel is open.

The extension did this

The background service worker reads every cookie the page has set and queues the record for upload to the developer's server.

When this fires

Every 2 seconds · flushes at 50 queued events

Every 4 seconds it re-reads cookies for every open tab. Every 2 seconds it uploads whatever has queued up since, in batches of up to 50 records, on its own timer rather than in response to anything you do.

What's sent in each cookie record
  • Page you visited
    https://www.nytimes.com/2026/09/12/world/example-article.html

    The exact address of the page you're on is attached to every cookie record sent.

  • Cookie's own domain
    doubleclick.net

    The site the cookie belongs to, which can be a different domain than the page you're viewing.

  • Cookie name
    _ga

    The specific cookie's name, which can reveal which tracker or login system set it.

  • Login-only flag
    true

    Marks cookies that are inaccessible to page scripts, a pattern common to session and login tokens.

  • Cookie size
    182

    The combined length of the cookie's name and value. The value itself isn't sent, only this count.

  • Consent status
    unknown

    The cookie-consent state recorded for the page. It's attached to the record but doesn't affect whether the record is sent.

The code that does this

The cookie collector and its upload loop

Readable version
function getResourceUrls(e) {  return new Promise(r => {    chrome.scripting.executeScript({      target: {        tabId: e.id      },      function: () => performance.getEntriesByType("resource").map(e => e.name)    }, e => {      if (chrome.runtime.lastError || !e?.[0]?.result) return r([]);      if (!Array.isArray(e[0].result)) return r([]);      try {        var t = e[0].result.map(e => e.split(/[#?]/)[0]);        r([...new Set(t)])      } catch {        r([])      }    })  })}function getCookiesForUrl(e, {  consent: a,  tabUrl: n,  dedupe: i}) {  return new Promise(r => {    try {      chrome.cookies.getAll({        url: e      }, e => {        try {          var t = (e || []).filter(e => {            e = e.domain + "|" + e.name;            return !i.has(e) && (i.add(e), !0)          }).map(e => ({            c: a,            h: n.hostname,            n: e.name,            d: e.domain,            e: e.session ? 0 : parseInt(e.expirationDate, 10),            s: e.name.length + e.value.length,            o: e.httpOnly ? 1 : 0,            u: n.href          }));          r(t)        } catch {          r([])        }      })    } catch {      r([])    }  })}async function callCmpFunction(e) {  var [{    result: e  }] = await chrome.scripting.executeScript({    target: {      tabId: e    },    world: "MAIN",    func: async () => window.__COMMANDERS_ACT_ASSISTANT_CMP_BRIDGE_RUNNING__STATUS__.getConsentStatus()  });  return e}async function getTabCookies(e) {  let a;  try {    a = new URL(e.url)  } catch {    return  }  let n = sidByTabId.get(e.id);  if (n) {    let t = sidDedupeSet(n);    var i = await getResourceUrls(e);    let r = (await callCmpFunction(e.id))?.consent || CMP_STATUS.UNKNOWN;    e = (await Promise.all(i.map(async e => (await getCookiesForUrl(e, {      consent: r,      tabUrl: a,      dedupe: t    })).map(e => ({      ...e,      sid: n    }))))).flat();    cookiesQueue = cookiesQueue.concat(e)  }}chrome.runtime.onConnect.addListener(e => {  if ("caa_cmp" === e.name) {    let r = e.sender?.tab?.id;    r && e.onMessage.addListener(e => {      var t;      "CMP_STATE" === e?.type && (t = sidByTabId.get(r)) && (e = normalizeCmpStatus(e?.payload?.status), consentBySid.set(t, e))    })  }});let cookiesCollector = setInterval(() => {    chrome.tabs.query({      currentWindow: !0    }, e => {      e.forEach(async e => {        e?.id && e.url && /^https?:\/\//.test(e.url) && "complete" === e.status && getTabCookies(e)      })    })  }, 4e3),  cookiesSaver = (chrome.tabs.onUpdated.addListener(async (t, e, r) => {    if (t && r?.url && /^https?:\/\//.test(r.url))      if ("loading" === e.status) {        var a = sidByTabId.get(t);        a && (purgeQueueBySid(a), cookiesCacheBySid.delete(a), consentBySid.delete(a));        let e = "";        try {          e = new URL(r.url).hostname        } catch {}        a = newSid(e);        sidByTabId.set(t, a), consentBySid.set(a, CMP_STATUS.UNKNOWN), void cookiesCacheBySid.set(a, new Set)      } else if ("complete" === e.status) {      r = sidByTabId.get(t);      if (r) try {        await injectScript(t, "cmp_consent_tracker"), await chrome.tabs.sendMessage(t, {          type: "CAA_SID_SET",          sid: r        })      } catch (e) {}    }  }), chrome.tabs.onRemoved.addListener(e => {    var t = sidByTabId.get(e);    sidByTabId.delete(e), t && (purgeQueueBySid(t), cookiesCacheBySid.delete(t), consentBySid.delete(t))  }), setInterval(async () => {    try {      for (; 0 < cookiesQueue.length;) {        var e = 50 < cookiesQueue.length ? 50 : cookiesQueue.length,          t = cookiesQueue.splice(0, e);        await fetch(await getCollectorUrl(), {          method: "POST",          headers: {            "Content-Type": "application/json;charset=UTF-8"          },          body: JSON.stringify(t)        })      }    } catch (e) {}  }, 2e3));
Where the data goes
    • privacy.trustcommander.net

    Runs the collector this extension uploads to. Same vendor family as commandersact.com, the Commanders Act consent-management product this extension assists with.

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 Commanders Act Assistant 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.

  • platform.commandersact.com

    Commanders Act Assistant sends data to platform.commandersact.com. No other extension we have analysed sends data here.

  • privacy.trustcommander.net

    Commanders Act Assistant sends data to privacy.trustcommander.net. No other extension we have analysed sends data here.

Updated 30 September 2026lfaifjhjdolnpnlgeohohaalbeidhlpj