Is Pocket Universe safe?
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.…
Who publishes itKerberus Cyber Security, Inc. - 1 other listing from the same operator, none carrying a finding
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.
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
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 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.
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 injection-eligibility and cookie/localStorage-write logic
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 };}- Referral-attribution cookiepm_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 lifetimemaxAgeDays: 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.
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
{ "cookie: pm_referral_attribution": "Kerberus", "localStorage: pm_referral_attribution": "{\"code\":\"Kerberus\",\"ts\":2102185841504}"}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.
- Node.js 18+
// 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);});- 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
You copy or paste text containing a wallet address while browsing a regular web page.
The extension extracts the address and sends it with the page hostname and full URL for a remote clipboard check.
- Clipboard wallet address0x742d35Cc6634C0532925a3b844Bc454e4438f44e
Shows the on-chain account value you copied, pasted, or that a page tried to place on your clipboard.
- Clipboard sourcepaste
Shows whether the address came from copy, paste, or a page-initiated clipboard write.
- Page hostnameapp.uniswap.org
Identifies which website was open when the wallet address was detected.
- Full page URLhttps://app.uniswap.org/swap
Can reveal the exact page or workflow you were using when the address was checked.
The background code posts clipboard-check payloads to this endpoint; the prior traffic run did not capture a matching request.
Clipboard interception and background forwarding path
Formatted deobfuscated functions
inject.chrome.11349145.js, requests.45731acc.js, static/background/index.jsfunction 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" } } }Content script coverage for regular web pages
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": [] },- 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
You install or update Pocket Universe.
The extension builds a device fingerprint and sends it with a hashed public IP address to Kerberus telemetry.
Observed during dynamic analysis: the POST body indicated trigger=install, fingerprint fields, and a clientIpHash value.
- Hashed public IP addressa5ccce
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 hintsAmerica/New_York
Adds regional context about where your browser appears to be running.
- Browser and device profileChrome 126 on macOS, desktop, 1920x1080
Helps distinguish your browser from other users by combining software and device traits.
- Install triggerinstall
Marks whether the request happened during installation or an update.
Public IP lookup, hash, payload assembly, and telemetry POST
Formatted deobfuscated functions
static/background/index.jsasync 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" })) } }- 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