Is Plus: Live screenshots of any app or website safe?

Medium risk

Plus collects cookies from every third-party domain a captured page's resources come from, then uploads them with each snapshot.

When a user takes a screenshot ('Take a Snapshot') of a page, Plus reads the list of every third-party domain that page loaded resources from (via the browser's performance API) and calls chrome.cookies.getAll on each one, collecting cookie name, value, domain, and expiry data that page scripts on those third-party sites could not read themselves. This cookie data is bundled into the snapshot's session payload, encrypted on the device, and uploaded to the vendor's backend (an AWS API Gateway/S3 endpoint) alongside the captured page's URL. The extension also injects its extension ID and version into every page's main-world window object, letting any website detect that this extension is installed.

45Risk
Who publishes it

Plus Docs Inc - no other listings under this identity, 2 shared hostnames

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

Publisher
Plus
Declared legal entity
Plus Docs Inc
Registered address
113 Cherry St PMB 86703, Seattle, WA 98104-2205, US
Registered contact
Plus Docs Inc

Shared hosts - 2 hostnames

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.

events.statsigapi.net
Also called by 3 other listings, including Guppy - Collect smarter with AI., Supernormal
featuregates.org
Also called by 3 other listings, including Guppy - Collect smarter with AI., Supernormal

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

Publishers can request a review.

Findings

SeverityHIGH
ClassUNWANTED
TypeUnexpected
CWECWE-522
SourceAI FOUND

Snapshot Feature Pulls Cookies From Every Third-Party Domain a Page Loads

Code analysis shows the Take a Snapshot feature requests cookies not only from the page you capture, but from every third-party domain that page loaded a script, font, or widget from, then uploads them, encrypted, with the snapshot.

01EvidenceCAUSE EFFECT
What actually happens
You did this

You take a snapshot of a page that loads at least one third-party resource, such as a chat widget, font, or analytics script.

The extension did this

The extension collects cookies not just for that page, but for every one of those third-party domains too, and bundles them into the snapshot it uploads.

The set of domains queried is whatever performance.getEntriesByType('resource') happens to list, not a domain you chose or reviewed.

02EvidenceCODE COMPARE
The code that does this

The three functions that turn one page capture into cookie reads across many domains

What it actually does
content/index.js: builds the domain list from the page's own subresourcescontent/index.js
const buildSessionPayload = async (storageToCapture) => {
  const indexedDatabases = shouldCaptureIndexedDb(storageToCapture) ? await readIndexedDb() : null;

  // Every subresource this page loaded: scripts, fonts, images, XHR/fetch, iframes...
  const resourceDomains = performance.getEntriesByType('resource').map((entry) => entry.name);

  return {
    sessionPayload: {
      session: {
        // Ask the background service worker for cookies from the page URL
        // AND every one of those resource domains, not just the page itself.
        cookies: await new Promise((resolve) => {
          chrome.runtime.sendMessage(
            buildMessage(MessagesFromRecordingTab.GetCookies, {
              domains: resourceDomains,
              url: window.location.href,
            }),
            (response) => resolve(response),
          );
        }),
        indexedDatabases,
        localStorage: readLocalStorage(storageToCapture, localStorage),
        sessionStorage: readSessionStorage(storageToCapture, sessionStorage),
        userAgent: navigator.userAgent,
      },
      v: '1',
    },
    v: '3',
  };
};
background.js:78: routes the request to the cookie-collection strategybackground.js
if (messageType === MessagesFromRecordingTab.GetCookies) {
  const { url: pageUrl, domains: resourceDomains } = message.data;
  return getCookiesForDomains([StorageStrategy.COOKIES], pageUrl, resourceDomains)
    .then((cookies) => sendResponse(cookies))
    .catch((err) => { throw err; }),
    true; // keep the message channel open for the async sendResponse
}
chunks/domainStrategies-42e5b698.js: expands to every parent-domain suffix, then queries each onechunks/domainStrategies-42e5b698.js
// Every parent-domain suffix of a hostname:
// "widget.intercom.io" -> ["widget.intercom.io", "intercom.io", "io"]
function parentDomainSuffixes(url) {
  if (!url || !/^https?:\/\//i.test(url)) return [];
  const { hostname } = new URL(url);
  const parts = hostname.split('.');
  return parts.slice(0, parts.length - 1).map((_, i) => parts.slice(i, parts.length).join('.'));
}

async function getCookiesForDomains(storageTypes, pageUrl, resourceDomains) {
  if (!storageTypes.includes(StorageStrategy.COOKIES)) return [];
  const cookies = [];
  // Union of every suffix of the page URL AND every resource domain
  const domainsToQuery = new Set([pageUrl, ...resourceDomains].map(parentDomainSuffixes).flat());
  for (const domain of domainsToQuery) {
    const found = await chrome.cookies.getAll({ domain });
    cookies.push(...found);
  }
  return cookies;
}
03EvidenceFIELD TABLE
What chrome.cookies.getAll returns for each expanded domain
FieldValueWhy it matters
Cookie name
SIDThe cookie's identifier, e.g. a login session token or an ad-tracking ID.
Cookie value
g.a000rwB3x9k2QzMLp7VvY6TqW1n8sD4fH2jK5mN0The actual token; some services accept it to restore a logged-in session if replayed elsewhere.
Cookie domain
accounts.google.comWhich site set the cookie; can be a third party embedded on the page rather than the page itself.
HttpOnly
trueWhen true, a page's own JavaScript normally cannot read this cookie; chrome.cookies.getAll bypasses that.
Expiry
2027-03-14T00:00:00ZHow long the cookie, and any session it represents, stays valid after collection.
04EvidenceTHIRD PARTY LIST
Where the collected cookies go
  • sfd8q2ch3k.execute-api.us-east-2.amazonaws.com

    Vendor's GraphQL API (createSnapshot/updateSnapshot); returns a presigned S3 URL the browser PUTs the encrypted snapshot, including the bulk-collected cookies, to.

05EvidenceARTIFACT
Reproduce it yourself

Runs the same domain-suffix expansion the background script performs, so you can see how many unrelated domains would have their cookies pulled for one page capture.

RequiresNode.js 18+
plus-cookie-scope-reproducer.js · js
// Reproduces the domain-expansion logic in chunks/domainStrategies-42e5b698.js.
// Feed it a page URL plus the resource URLs performance.getEntriesByType('resource')
// would report for that page, and it prints every domain chrome.cookies.getAll
// would be called with during a "Take a Snapshot" capture.

function parentDomainSuffixes(url) {
  if (!url || !/^https?:\/\//i.test(url)) return [];
  const { hostname } = new URL(url);
  const parts = hostname.split('.');
  return parts.slice(0, parts.length - 1).map((_, i) => parts.slice(i, parts.length).join('.'));
}

function domainsQueriedForCookies(pageUrl, resourceUrls) {
  const all = [pageUrl, ...resourceUrls].map(parentDomainSuffixes).flat();
  return [...new Set(all)];
}

// Illustrative example: a SaaS dashboard that embeds Intercom chat and Google Fonts.
const pageUrl = 'https://app.example-saas.com/dashboard';
const resourceUrls = [
  'https://app.example-saas.com/static/app.js',
  'https://widget.intercom.io/widget/abc123',
  'https://fonts.googleapis.com/css?family=Inter',
  'https://www.google-analytics.com/analytics.js',
];

const domains = domainsQueriedForCookies(pageUrl, resourceUrls);
console.log(`chrome.cookies.getAll would be called for ${domains.length} domains:`);
domains.forEach((d) => console.log(' -', d));
How to run it
  1. 1
    node plus-cookie-scope-reproducer.js
06EvidencePLAIN NOTE
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.

What it can do

Permissions this extension asks for, as declared in version 4.9.1. 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 every site you visit

    *://*/*

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

    cookies

  • Change your browser's font settings

    fontSettings

  • Store data in your browser

    storage

  • Watch every request your browser makes

    webRequest

Where it sends data

Destinations our analysis observed Plus: Live screenshots of any app or website 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.

  • sfd8q2ch3k.execute-api.us-east-2.amazonaws.com

    Plus: Live screenshots of any app or website sends data to sfd8q2ch3k.execute-api.us-east-2.amazonaws.com. No other extension we have analysed sends data here.

Updated 30 September 2026bnebanooamokkihfjepphafoekheipfh