Is Pocket Universe safe?

Medium risk

Pocket Universe is medium risk. Loading polymarket.com triggers a background fetch of config from conf.kerberus.com and a flag check before a content script writes a referral cookie and matching localStorage entry naming the developer, Kerberus. Our test had the flag on.…

Kerberus Cyber Security, Inc.v2.28.0Chrome Web Store
52Risk
Who publishes it

Kerberus Cyber Security, Inc. - 1 other listing from the same operator, none carrying a finding

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

Publisher
Kerberus Cyber Security, Inc.
Declared legal entity
Kerberus Cyber Security, Inc.
Registered address
254 Chapman Rd ste 209, Newark, DE 19702, US
Registered contact
Kerberus Cyber Security, Inc.

Same store account

1 other listing published from this account, 30k+ users between them, none of them carrying a finding.

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

Publishers can request a review.

Findings

Server-Controlled Referral Cookie Injected on Polymarket

Loading polymarket.com triggers a background fetch of config from conf.kerberus.com and a flag check before a content script writes a referral cookie and matching localStorage entry naming the developer, Kerberus.

Our test had the flag on.

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

You open polymarket.com in a tab with this extension installed.

The content script is scoped to *://polymarket.com/* and runs at document_idle, after the page's own scripts have run.

The extension did this

The extension checks a live server flag and, when it is enabled, writes a referral-attribution cookie and localStorage value naming the extension's own developer onto the page.

The written value carries an expiration date roughly ten years out, so it does not appear to expire during normal browsing.

Captured request
GEThttps://conf.kerberus.com/killswitch.json

The response body's flags.polymarketClientSideInjections object had enabled=true, with a cookie spec and a localStorage spec both named pm_referral_attribution, value "Kerberus", and a 3650-day (~10 year) future timestamp offset for the localStorage record.

The code that does this

The injection-eligibility and cookie/localStorage-write logic

Readable version
function readInjectionsConfig(flags) {  let parsed = InjectionsSchema.safeParse(flags?.polymarketClientSideInjections);  return parsed.success ? parsed.data : null;}function shouldWrite(currentValue, targetValue, overwriteMode) {  return (    overwriteMode === "always" ||    currentValue === undefined ||    currentValue === "" ||    (overwriteMode !== "ifAbsent" && currentValue === targetValue)  );}function buildCookieString(cookieSpec) {  let str = `${cookieSpec.name}=${cookieSpec.value}; path=${cookieSpec.path}; max-age=${86400 * cookieSpec.maxAgeDays}; samesite=${cookieSpec.sameSite}`;  if (cookieSpec.sameSite === "none") str += "; secure";  return str;}function buildLocalStorageValue(lsSpec, now) {  let record = { ...lsSpec.value };  if (lsSpec.ts && lsSpec.ts.mode !== "none") {    record[lsSpec.ts.field] = lsSpec.ts.mode === "future"      ? now + 86400000 * lsSpec.ts.offsetDays      : now;  }  return JSON.stringify(record);}function applyInjections(config, storageAdapter, now) {  let cookiesWritten = 0, localStorageWritten = 0;  let currentCookies = storageAdapter.getCookieString();  let lsByKey = new Map(config.localStorage.map(entry => [entry.key, entry]));  for (let cookieSpec of config.cookies) {    let lsEntry = lsByKey.get(cookieSpec.name);    let currentValue = readCurrentReferralCode(      currentCookies,      lsEntry ? storageAdapter.getLocalStorage(lsEntry.key) : null,      cookieSpec.name,      lsEntry?.identityField ?? "code",      now    );    if (shouldWrite(currentValue, cookieSpec.value, cookieSpec.overwrite)) {      storageAdapter.setCookie(buildCookieString(cookieSpec));      cookiesWritten++;    }  }  for (let lsSpec of config.localStorage) {    let currentValue = readCurrentReferralCode(currentCookies, storageAdapter.getLocalStorage(lsSpec.key), lsSpec.key, lsSpec.identityField, now);    let targetValue = String(lsSpec.value[lsSpec.identityField] ?? "");    if (shouldWrite(currentValue, targetValue, lsSpec.overwrite)) {      storageAdapter.setLocalStorage(lsSpec.key, buildLocalStorageValue(lsSpec, now));      localStorageWritten++;    }  }  return { cookiesWritten, localStorageWritten };}
What the remote config told the extension to write on polymarket.com
  • Referral-attribution cookie
    pm_referral_attribution=Kerberus

    A cookie the site uses to credit your visit gets set to the developer's own code, overwriting any prior value if the conditions below hold.

  • Matching localStorage entry
    {"code":"Kerberus","ts":2102185841504}

    The same code is duplicated into localStorage with a far-future timestamp, so a freshness check wouldn't treat it as expired.

  • Write condition (overwrite policy)
    ifAbsentOrMine (observed default); always (supported by the config schema)

    By default the extension writes this only when the slot is empty or matches its own code; an 'always' mode would overwrite a different code.

  • Value lifetime
    maxAgeDays: 400 (cookie); offsetDays: 3650 (localStorage timestamp)

    The cookie expires in ~400 days; the localStorage record's timestamp is set ~10 years out, so neither is treated as stale.

What's stored on your device

The referral record found live after installing and visiting polymarket.com with the flag on: a code naming the developer, timestamped 2036.

Location
document.cookie and window.localStorage on polymarket.com
Contents
{  "cookie: pm_referral_attribution": "Kerberus",  "localStorage: pm_referral_attribution": "{\"code\":\"Kerberus\",\"ts\":2102185841504}"}
Reproduce it yourself

Fetches the extension's remote configuration endpoint and reports whether the Polymarket referral/localStorage injection is currently enabled server-side, without needing the extension installed.

Requires
  • Node.js 18+
check_killswitch_flag.js · js
// check_killswitch_flag.js// Usage: node check_killswitch_flag.jsconst url = "https://conf.kerberus.com/killswitch.json";async function main() {  const res = await fetch(url);  if (!res.ok) {    console.error(`Request failed: ${res.status} ${res.statusText}`);    process.exit(1);  }  const data = await res.json();  const spec = data?.flags?.polymarketClientSideInjections;  if (!spec) {    console.log("No polymarketClientSideInjections flag present in the current remote config.");    return;  }  console.log(`enabled: ${spec.enabled}`);  if (spec.rolloutStartTs) {    console.log(`rolloutStartTs: ${spec.rolloutStartTs}`);    console.log(`rolloutDays: ${spec.rolloutDays ?? 10}`);  }  for (const c of spec.cookies ?? []) {    console.log(`cookie spec: name=${c.name} value=${c.value} overwrite=${c.overwrite ?? "ifAbsentOrMine"} maxAgeDays=${c.maxAgeDays ?? 400}`);  }  for (const l of spec.localStorage ?? []) {    console.log(`localStorage spec: key=${l.key} identityField=${l.identityField ?? "code"} value=${JSON.stringify(l.value)} overwrite=${l.overwrite ?? "ifAbsentOrMine"}`);  }}main().catch((err) => {  console.error(err);  process.exit(1);});
How to run it
  1. 1Install Node.js 18+, run node check_killswitch_flag.js, and read the printed enabled value and cookie/localStorage specs, the live config the extension reads on the next page load.

Clipboard wallet addresses sent for Kerberus check

Code analysis shows Pocket Universe watches copy, paste, and clipboard writes on web pages.

If clipboard text is one wallet address, it sends the address, source action, hostname, and URL to the worker, which posts to api.kerberus.com.

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

You copy or paste text containing a wallet address while browsing a regular web page.

The extension did this

The extension extracts the address and sends it with the page hostname and full URL for a remote clipboard check.

Fields prepared for the clipboard-check call
  • Clipboard wallet address
    0x742d35Cc6634C0532925a3b844Bc454e4438f44e

    Shows the on-chain account value you copied, pasted, or that a page tried to place on your clipboard.

  • Clipboard source
    paste

    Shows whether the address came from copy, paste, or a page-initiated clipboard write.

  • Page hostname
    app.uniswap.org

    Identifies which website was open when the wallet address was detected.

  • Full page URL
    https://app.uniswap.org/swap

    Can reveal the exact page or workflow you were using when the address was checked.

Captured request
POSThttps://api.kerberus.com/v1/extension/clipboard/check

The background code posts clipboard-check payloads to this endpoint; the prior traffic run did not capture a matching request.

The code that does this

Clipboard interception and background forwarding path

Readable version

Formatted deobfuscated functions

inject.chrome.11349145.js, requests.45731acc.js, static/background/index.js
function l() {        try {          ! function() {            let e = "undefined" != typeof navigator ? navigator.clipboard : void 0;            if (!e) return;            let t = "function" == typeof e.writeText ? e.writeText.bind(e) : void 0;            if (t) {              let r = async function(e) {                if ("string" == typeof e && e.length > 0) try {                  (0, o.messaging).fire("clipboardWrite", {                    text: e,                    kind: "writeText"                  })                } catch {}                try {                  return await t(e)                } catch (e) {                  throw (0, i.sanitizeStack)(e)                }              };              (0, s.spoofToString)(r, "writeText", e.writeText);              try {                e.writeText = r              } catch {}            }            let r = "function" == typeof e.write ? e.write.bind(e) : void 0;            if (r) {              let t = async function(e) {                try {                  for (let t of e)                    if (t.types.includes("text/plain")) {                      let e = await t.getType("text/plain"),                        r = await e.text();                      if (r) try {                        (0, o.messaging).fire("clipboardWrite", {                          text: r,                          kind: "write"                        })                      } catch {}                    }                } catch {}                try {                  return await r(e)                } catch (e) {                  throw (0, i.sanitizeStack)(e)                }              };              (0, s.spoofToString)(t, "write", e.write);              try {                e.write = t              } catch {}            }          }()        } catch (e) {          (0, a.logInjected).warn("[clipboard-guard] patch install failed", {            error: e instanceof Error ? e.message : String(e)          })        }      }function f(e, t) {      if (!g || !e) return;      let r = (0, u.detectAddresses)(e);      if (1 !== r.length) return;      let n = r[0],        a = `${n.chain}:${n.address}`;      if (function(e) {          let t = p.get(e);          return void 0 !== t && (!(Date.now() >= t) || (p.delete(e), !1))        }(a)) {        (0, i.logContentScript).debug("[clipboard-guard] Burst dedup — skipping duplicate capture", {          source: t,          chain: n.chain        });        return      }      if (p.set(a, Date.now() + 1e3), c) {        if (m.has(n.address) || d.has(a) || d.size >= 100) return;        d.add(a)      }(0, i.logContentScript).debug("[clipboard-guard] Detected address", {        source: t,        chain: n.chain      }), (0, o.sendToBackgroundClipboardCheck)({        addresses: [n.address],        source: t,        hostname: window.location.hostname,        originUrl: window.location.href      }).catch(e => {        (0, i.logContentScript).warn("[clipboard-guard] Background check failed", {          error: e instanceof Error ? e.message : String(e)        })      })    }v = async e => {        try {          let t = await (0, o.fetchWithHeaders)("/v1/extension/clipboard/check", {            method: "POST",            body: JSON.stringify(e)          }, {            root: !0          });          if (!t.ok) return (0, a.logServer).warn(`Clipboard check non-OK status: ${t.status}`), {            status: "ok"          };          let r = await t.json();          if (r && ("ok" === r.status || "suspicious_poisoning" === r.status || "suspicious_phishing" === r.status)) return r;          return (0, a.logServer).warn("Clipboard check returned unexpected payload"), {            status: "ok"          }        } catch (e) {          return (0, a.logServer).warn("Failed to call clipboard check", {            error: e instanceof Error ? e.message : String(e)          }), {            status: "ok"          }        }      }
The code that does this

Content script coverage for regular web pages

Readable version

Manifest content-script entry

manifest.json
    {      "matches": [        "http://*/*",        "https://*/*"      ],      "js": [        "requests.45731acc.js"      ],      "exclude_matches": [        "*://localhost/*",        "*://127.0.0.1/*",        "*://google.com/*",        "*://*.google.com/*",        "*://youtube.com/*",        "*://*.youtube.com/*",        "*://*.googlesyndication.com/*",        "*://*.googletagmanager.com/*",        "*://*.doubleclick.net/*",        "*://reddit.com/*",        "*://*.reddit.com/*",        "*://whatsapp.com/*",        "*://*.whatsapp.com/*",        "*://facebook.com/*",        "*://*.facebook.com/*",        "*://*.messenger.com/*",        "*://*.delawareonline.com/*",        "*://*.usatoday.com/*",        "*://temu.com/*",        "*://*.temu.com/*",        "*://amazon.com/*",        "*://*.amazon.com/*",        "*://*.amazon-adsystem.com/*",        "*://tcgrepublic.com/*",        "*://*.tcgrepublic.com/*",        "*://binance.com/*",        "*://*.binance.com/*",        "*://megacloud.tv/*",        "*://*.megacloud.tv/*",        "*://vide63.com/*",        "*://*.vide63.com/*",        "*://chatgpt.com/*",        "*://*.chatgpt.com/*",        "*://note.com/*",        "*://*.note.com/*",        "*://authena.xyz/*",        "*://*.authena.xyz/*",        "*://zkpass.org/*",        "*://*.zkpass.org/*",        "*://proxy6.net/*",        "*://*.proxy6.net/*",        "*://*.taboola.com/*",        "*://challenges.cloudflare.com/*",        "*://www.recaptcha.net/*",        "*://*.recaptcha.net/*",        "*://hcaptcha.com/*",        "*://*.hcaptcha.com/*",        "*://arkoselabs.com/*",        "*://*.arkoselabs.com/*",        "*://datadome.co/*",        "*://*.datadome.co/*",        "*://perimeterx.net/*",        "*://*.perimeterx.net/*",        "*://datacrazy.io/*",        "*://*.datacrazy.io/*",        "*://*.navyfederal.org/*",        "*://*.dcinside.com/*",        "*://*.360safe.com/*",        "*://*.socialsecurity.be/*",        "*://dpoczta.pl/*",        "*://*.dpoczta.pl/*",        "*://*.jal.co.jp/*",        "*://*.utoronto.ca/*"      ],      "run_at": "document_start",      "all_frames": true,      "css": []    },
Remote destination for clipboard checks
    • api.kerberus.com

    Receives clipboard-check requests containing detected wallet addresses and page context.

Install sends fingerprint and hashed IP to Kerberus

We observed Pocket Universe sending install telemetry to telemetry.kerberus.com.

The code fetches the public IP from ipinfo.io or api.ipify.org, hashes it SHA-256, combines it with fingerprint fields, and posts it to the telemetry endpoint.

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

You install or update Pocket Universe.

The extension did this

The extension builds a device fingerprint and sends it with a hashed public IP address to Kerberus telemetry.

Captured request
POSThttps://telemetry.kerberus.com/v1/user

Observed during dynamic analysis: the POST body indicated trigger=install, fingerprint fields, and a clientIpHash value.

Fields assembled for the telemetry request
  • Hashed public IP address
    a5ccce

    Links the install event to a stable value derived from your public network address without sending the raw IP in this request.

  • Timezone and location hints
    America/New_York

    Adds regional context about where your browser appears to be running.

  • Browser and device profile
    Chrome 126 on macOS, desktop, 1920x1080

    Helps distinguish your browser from other users by combining software and device traits.

  • Install trigger
    install

    Marks whether the request happened during installation or an update.

The code that does this

Public IP lookup, hash, payload assembly, and telemetry POST

Readable version

Formatted deobfuscated functions

static/background/index.js
async function h() {      let e = {        ip: null,        city: null,        countryCode: null,        geoTimezone: null      };      try {        let t = new AbortController,          r = setTimeout(() => t.abort(), o.EXTERNAL_API_TIMEOUT_MS),          n = await fetch("https://ipinfo.io/json", {            signal: t.signal          });        if (clearTimeout(r), n.ok) {          let t = await n.json();          return e.ip = t.ip || null, e.city = t.city || null, e.countryCode = t.country || null, e.geoTimezone = t.timezone || null, e        }      } catch (e) {        (0, i.logTelemetry).warn("ipinfo.io failed, trying fallback:", e)      }      try {        let t = new AbortController,          r = setTimeout(() => t.abort(), o.EXTERNAL_API_TIMEOUT_MS),          n = await fetch("https://api.ipify.org?format=json", {            signal: t.signal          });        if (clearTimeout(r), n.ok) {          let t = await n.json();          e.ip = t.ip || null        }      } catch (e) {        (0, i.logTelemetry).warn("Could not fetch public IP:", e)      }      return e    }async function g(e) {      let t = new TextEncoder,        r = t.encode(e),        n = await crypto.subtle.digest("SHA-256", r),        o = Array.from(new Uint8Array(n));      return o.map(e => e.toString(16).padStart(2, "0")).join("")    }async function x(e) {      (0, i.logTelemetry).info("Calling API...");      let t = await (0, a.fetchWithHeaders)("/v1/user", {        method: "POST",        body: JSON.stringify(e)      }, {        telemetry: !0      });      if ((0, i.logTelemetry).info("API response status:", t.status), !t.ok) {        let e = await t.text();        throw t.status >= 500 ? (0, i.logTelemetry).warn(`Create user server error (HTTP ${t.status}): ${e}`) : t.status >= 400 ? (0, i.logTelemetry).error(`Create user client error (HTTP ${t.status}): ${e}`) : (0, i.logTelemetry).error(`Create user unexpected status (HTTP ${t.status}): ${e}`), Error(`Failed to create telemetry user: ${t.status}`)      }      return t.json()    }async function w(e) {      try {        let [t, r] = await Promise.all([(0, l.getTelemetryUserId)(), (0, l.getTelemetryAppId)()]), n = !(t && r), o = {          telemetryUserId: null,          telemetryAppId: null,          deviceId: null,          screenResolution: null,          detectedExtensions: null,          addressableUserId: null,          referralCode: null,          affiseClickId: null        };        try {          o = await (0, u.getKerberusTelemetryData)({            waitForMixpanel: n          })        } catch (e) {          (0, i.logTelemetry).warn("Failed to get kerberus localStorage", {            error: e          })        }        if ("install" === e && o.referralCode) try {          await (0, l.setInstallReferralCode)(o.referralCode)        } catch (e) {          (0, i.logTelemetry).warn("Failed to persist install referral code", {            error: e          })        }        let a = function(e) {          if (!e || 0 === e.length) return {            walletsExtensions: [],            securityExtensions: []          };          let t = new Set,            r = new Set;          for (let n of e) "Wallets" === n.group && n.name ? t.add(n.name) : "Security" === n.group && n.name && r.add(n.name);          return {            walletsExtensions: [...t],            securityExtensions: [...r]          }        }(o.detectedExtensions);        if (t && r) {          if (!o.telemetryUserId || !o.telemetryAppId) {            (0, i.logTelemetry).info("IDs exist in Chrome storage but missing in kerberus.com localStorage, syncing...");            try {              let [e, n] = await Promise.all([(0, u.setKerberusLocalStorage)("telemetryUserId", t), (0, u.setKerberusLocalStorage)("telemetryAppId", r)]);              e && n ? (0, i.logTelemetry).info("Successfully synced existing IDs to kerberus.com localStorage") : (0, i.logTelemetry).warn("Failed to sync some existing IDs to kerberus.com localStorage", {                userIdSynced: e,                appIdSynced: n              })            } catch (e) {              (0, i.logTelemetry).warn("Failed to sync existing IDs to kerberus localStorage", {                error: e              })            }          }          return a        }        let s = k();        o.screenResolution && !s.screenResolution && (s.screenResolution = o.screenResolution);        let d = await j();        s.browser = d.browser, s.browserVersion = d.browserVersion;        let f = await h(),          p = f.ip ? await g(f.ip) : void 0;        f.countryCode && (s.countryCode = f.countryCode), f.city && (s.city = f.city);        let m = {          userId: t || o.telemetryUserId || void 0,          appId: r || o.telemetryAppId || void 0,          app: {            appVersion: chrome.runtime.getManifest().version,            appType: "chrome_extension"          },          mixpanel: o.deviceId ? {            deviceId: o.deviceId          } : void 0,          fingerprint: s,          clientIpHash: p,          trigger: e,          ..."install" === e && o.addressableUserId ? {            addressableUserId: o.addressableUserId          } : {},          ..."install" === e && o.referralCode ? {            referralCode: o.referralCode          } : {},          ..."install" === e && o.affiseClickId ? {            affiseClickId: o.affiseClickId          } : {}        };        (0, i.logTelemetry).info("Creating user with request:", m);        let v = await x(m);        if ((0, i.logTelemetry).info("Telemetry user creation response", {            userId: v.userId,            appId: v.appId,            waitForJob: v.waitForJob,            reason: v.reason          }), v.waitForJob && v.jobId) {          let e = v.jobId;          for (let t = 0; t < 60; t++) {            await c(1e3);            let t = await b(e);            if ("completed" === t.status) break;            if ("failed" === t.status) {              if ((0, i.logTelemetry).warn("Telemetry job failed, retrying", {                  jobId: e,                  failedReason: t.failedReason                }), !(v = await x(m)).waitForJob || !v.jobId) break;              e = v.jobId            }          }        }        let y = [(0, l.setTelemetryUserId)(v.userId), (0, l.setTelemetryAppId)(v.appId), (0, l.setTelemetryFingerprint)(s)];        p && y.push((0, l.setTelemetryIpHash)(p)), await Promise.all(y);        try {          let [e, t] = await Promise.all([(0, u.setKerberusLocalStorage)("telemetryUserId", v.userId), (0, u.setKerberusLocalStorage)("telemetryAppId", v.appId)]);          e && t ? (0, i.logTelemetry).info("Successfully synced IDs to kerberus.com localStorage") : (0, i.logTelemetry).warn("Failed to sync some IDs to kerberus.com localStorage", {            userIdSynced: e,            appIdSynced: t          })        } catch (e) {          (0, i.logTelemetry).warn("Failed to sync to kerberus localStorage", {            error: e          })        }        if (m.referralCode || m.affiseClickId) try {          await Promise.all([(0, u.setKerberusLocalStorage)("ref", ""), (0, u.setKerberusLocalStorage)("affclick", "")]), (0, i.logTelemetry).info("Consumed Affise capture keys (ref/affclick) after successful install")        } catch (e) {          (0, i.logTelemetry).warn("Failed to consume Affise capture keys from kerberus localStorage", {            error: e          })        }        return (0, i.logTelemetry).info("Telemetry user initialized successfully", {          userId: v.userId,          appId: v.appId        }), a      } catch (e) {        return (0, i.logTelemetry).error("Failed to initialize telemetry user:", e), (0, i.logTelemetry).error("Failed to initialize telemetry user", {          error: e instanceof Error ? e.message : "Unknown error"        }), null      }    }async function E() {      try {        let [e, t, r] = await Promise.all([(0, l.getTelemetryUserId)(), (0, l.getTelemetryAppId)(), (0, l.getTelemetryIpHash)()]);        if (!e || !t) {          (0, i.logTelemetry).info("No user/app IDs available, skipping IP hash check");          return        }        let n = await h();        if (!n.ip) {          (0, i.logTelemetry).info("Could not fetch public IP, skipping IP hash check");          return        }        let o = await g(n.ip);        if (r === o) {          (0, i.logTelemetry).info("IP hash unchanged, skipping update");          return        }(0, i.logTelemetry).info("IP hash changed, updating backend"), await L(o, r || null), await (0, l.setTelemetryIpHash)(o), (0, i.logTelemetry).info("IP hash updated successfully")      } catch (t) {        let e = t instanceof TypeError && t.message.includes("Failed to fetch");        e ? (0, i.logTelemetry).debug("Failed to check/update IP hash - network unavailable") : ((0, i.logTelemetry).error("Failed to check/update IP hash:", t), (0, i.logTelemetry).warn("Failed to check/update IP hash", {          error: t instanceof Error ? t.message : "Unknown error"        }))      }    }
Network services used by this flow
    • telemetry.kerberus.com

    Receives the extension telemetry user payload containing the fingerprint and hashed IP value.

    • ipinfo.io

    First public-IP lookup service used to obtain IP and coarse geolocation fields.

    • api.ipify.org

    Fallback public-IP lookup service used when the ipinfo.io request fails.

+2 more findings not shown

Updated 30 September 2026gacgndbocaddlemdiaadajmlggabdeod