Is WhatRuns safe?
WhatRuns is high risk. Every time you navigate to a new site, WhatRuns sends its hostname and URL to whatruns.com/api/v1/get_site_apps, no prompt or opt-out. If logged in, email and API key go too, base64-encoded. Confirmed by five captured POSTs.…
Who publishes itOwnedit Ltd - no other listings under this identity, 2 shared hostnames
Ownedit Ltd - 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.
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.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Every site you visit is reported to WhatRuns servers
Every time you navigate to a new site, WhatRuns sends its hostname and URL to whatruns.com/api/v1/get_site_apps, no prompt or opt-out.
If logged in, email and API key go too, base64-encoded.
Confirmed by five captured POSTs.
- Severity
- High unwanted
- Type
- Unexpected
- CWE
- CWE-200
- Source
- Dynamic sandbox
You navigate to any website in your browser.
WhatRuns immediately sends that site's hostname and URL to its own servers, before you have finished loading the page.
This fires on every main_frame navigation, with no domain allowlist and no user prompt.
HTTP 200. Response includes technology fingerprint JSON and optional dom_data config object used to configure DOM scraping on subsequent page loads.
- Content-Type
- application/x-www-form-urlencoded
data=%7B%22rawhostname%22%3A%22github.com%22%2C%22hostname%22%3A%22github.com%22%2C%22url%22%3A%22https%3A%2F%2Fgithub.com%2F%22%2C%22encode%22%3Atrue%7D- The site you are visitinghttps://github.com/
The full URL of the page you just loaded, including any query parameters.
- Raw hostnamegithub.com
The exact hostname, including any subdomain, used to identify the specific site.
- Root hostnamegithub.com
The main domain without subdomain (e.g. 'github.com' instead of 'gist.github.com').
- Your email address (if logged in)dXNlckBleGFtcGxlLmNvbQ==
Your WhatRuns account email, base64-encoded and included in every request if you have ever signed in.
- Your API key (if logged in)YWJjMTIzZGVmNDU2
Your WhatRuns API key, base64-encoded. Uniquely identifies your account on every request.
The listener that fires on every navigation, from background.js
What the navigation listener does
// Fires every time any page finishes loading in any tab.browser.webRequest.onCompleted.addListener(function(details) { const url = normaliseURL(details.url); const rawHostname = extractHostname(url); // e.g. 'gist.github.com' sendURLToWhatRunsServer(details.tabId, rawHostname, url);}, { urls: ['http://*/*', 'https://*/*'], // every HTTP/HTTPS URL types: ['main_frame'] // top-level navigations only});How your URL is transmitted
function sendURLToWhatRunsServer(tabId, rawHostname, url) { // Build the payload with hostname + URL const payload = buildUrlPayload(url, rawHostname); // Attach your account email + API key if you're logged in (base64-encoded) const payloadWithAuth = attachCredentials(payload); // POST it to WhatRuns — unconditionally, on every navigation fetch('https://www.whatruns.com/api/v1/get_site_apps', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: 'data=' + encodeURIComponent(JSON.stringify(payloadWithAuth)) });}- www.whatruns.com
Primary server receiving every navigation event, owned by WhatRuns. Responds with technology-detection data and, for targeted sites, DOM scraping configuration objects.
WhatRuns posts page URL, title, referrer, and UUID
We observed WhatRuns POST to whatruns.com/api/v1/collect_data with the page URL, title, referrer, a UUID, and version.
In shipped v1.10.0, the flow builds these after a two-second timer, posting to api/v1/analyse/path.
- Severity
- Medium unwanted
- Type
- Unexpected
- CWE
- CWE-200
- Source
- Dynamic sandbox
You open a web page while WhatRuns is installed.
The content script runs on http and https pages at document idle.
The extension builds a page record and sends it to a WhatRuns API endpoint.
The record includes the page URL, title, referrer, a stored UUID, and the extension version.
- Current page URLhttps://www.amazon.com/
Shows the exact page you were viewing when the extension sent the request.
- Page titleAmazon.com. Spend less. Smile more.
Adds readable context about the page you visited, even when the URL alone is not descriptive.
- Referrerhttps://www.amazon.com/
Shows the page that led you to the current page when the browser provides one.
- Stored UUID3a4659a6dc0c468680cd66d0261bd6b0
Lets repeated page reports from the same browser installation be linked together over time.
- Extension version1.8.20
Identifies which WhatRuns build produced the report.
{ "url": "https://www.amazon.com/", "title": "Amazon.com. Spend less. Smile more.", "referrer": "https://www.amazon.com/", "uuid": "3a4659a6dc0c468680cd66d0261bd6b0", "plugin_version": "1.8.20"}The shipped code builds the page record and posts it to WhatRuns
Content script waits two seconds, then sends page fields
js/content.jsprimeFlow: async function() { try { var t = { id: FLOW_BOOT_SIGNAL }; let e = this; setTimeout(function() { e.pushRuntimeMessage(t, t => {}) }, 2e3) } catch (t) { console.log("content [primeFlow] error: ", t) }}flushBeacon: async function() { try { const e = { url: window.location.href, title: document ? document.title : "", referrer: document.referrer || "", uuid: await this.ensureRuntimeKey(), plugin_version: chrome.runtime.getManifest().version }; var t = { id: FLOW_ECHO_SIGNAL, data: e }; this.pushRuntimeMessage(t, t => {}) } catch (t) { console.log(t) }}ensureRuntimeKey: async function() { let t = await this.readStoreValue("wrs_session_uuid"); if (t) return t; let e = this.buildRuntimeKey(); return this.writeStoreValue("wrs_session_uuid", e), e}buildRuntimeKey: function() { const t = new Uint8Array(16); return crypto.getRandomValues(t), t[6] = 15 & t[6] | 64, t[8] = 63 & t[8] | 128, [...t].map((t, e) => "" + t.toString(16).padStart(2, "0")).join("")}Background worker serializes the data into a POST body
js/background.jsforwardRuntimeEcho: async function(t) { try { await _this.makeRequest({ type: "POST", url: ANALYSE_PATH, reqBody: t }) } catch (t) { console.log(t) }}activateRuntimeBridge: async function(t, e) { BROWSER.tabs.sendMessage(e, { id: FLOW_PRIME_SIGNAL, data: {} }, function(t) { return !0 })}makeRequest: async function(t) { let { type: e = "GET", url: a, headers: o = {}, reqBody: n = {} } = t, s = { status: 599, msg: "Error" }; try { "Content-Type" in o || (o["Content-Type"] = "application/json"); let t = { method: e, body: JSON.stringify(n), headers: o }; "GET" === e && delete t.body, await fetch(a, t).then(t => t.json()).then(t => { s = t }).catch(t => { console.log(t), s.msg = t }) } catch (t) { return console.log(3), void(s.msg = t.toString()) } return s}Constant resolving the current v1.10.0 destination
js/global.constants.jsexport const DOMAIN_NAME = "https://www.whatruns.com/";export const CDN_DOMAIN_NAME = "https://cdn.whatruns.com/";export const BROWSER = chrome || browser;export const ANALYSE_APPS = "analyseApps";export const GET_DETECTED_APPS = "getDetectedApps";export const GET_NOTIFICATION_MESSAGE = "getNotificationMessage";export const GET_HOST_NAME = "getHostName";export const SET_DATA = "setData";export const GET_DATA = "getData";export const GET_TECHS = "get_techs";export const GET_SITE_DATA = "get_site_data";export const KEY_DETAILS = "keyDetails";export const FORM = "form";export const GET_SITE_APPS = DOMAIN_NAME + "api/v1/get_site_apps";export const GET_SITE_APPS_BY_DATA = DOMAIN_NAME + "api/v1/get_site_apps_by_data";export const ANALYSE_EMAILS = DOMAIN_NAME + "api/v1/analyse_emails";export const REVIEW_FEATURE_DATA = DOMAIN_NAME + "api/v1/ext_review";export const invalidDomains = ["localhost", "127.0.0.1", "0.0.0.0"];export const NO_APPS_FOUND = " I feel lost, maybe there's nothing to be found ; ) ";export const ANALYSE_PAGE = DOMAIN_NAME + "api/v1/analyse/page";export const ANALYSE_PATH = DOMAIN_NAME + "api/v1/analyse/path";- www.whatruns.com
Receives the page-load record containing URL, title, referrer, stored UUID, and extension version.
Navigation beacon sends URL, title, referrer, and persistent ID to whatruns.com
Dynamic analysis captured POSTs to whatruns.com/api/v1/analyse/path on every navigation: URL, title, referrer, and a persistent UUID from storage.local.
The UUID recurred on unrelated sites (facebook, github, wikipedia), linking history.
- Severity
- Medium unwanted
- Type
- Unexpected
- CWE
- CWE-359
- Source
- Dynamic sandbox
You visit any webpage.
The content script runs on every page due to the extension's <all_urls> match pattern.
The extension sends your page URL, title, referrer, and a persistent tracking ID to whatruns.com.
Two seconds after page load, background.js POSTs this data to https://www.whatruns.com/api/v1/analyse/path. The same UUID persists across all sites, linking visits into a cross-site browsing history.
HTTP 200. Same UUID observed across 7+ navigations to unrelated domains in our test session (observed during dynamic analysis).
- Content-Type
- application/json
{ "url": "https://github.com/torvalds/linux", "title": "torvalds/linux: Linux kernel source tree", "referrer": "https://github.com", "uuid": "d45bfbf41ccd49899dddebba958c111b", "plugin_version": "1.10.0"}- Page URLhttps://github.com/torvalds/linux
The full address of every page you visit, including paths and query parameters.
- Page titletorvalds/linux: Linux kernel source tree
The document title of the page, often revealing the topic or content you were viewing.
- Referrerhttps://github.com
The URL of the page you came from, revealing navigation patterns and sequences.
- Persistent UUIDd45bfbf41ccd49899dddebba958c111b
A randomly generated ID stored permanently in your browser, sent on every page load, letting the server link your visits into one history.
- Extension version1.10.0
The version of the WhatRuns extension installed.
Written once, never rotated; read at the start of every beacon call, so the same value is sent each session until uninstall or manual clear.
- Location
- chrome.storage.local key 'wrs_session_uuid'
{ "wrs_session_uuid": "d45bfbf41ccd49899dddebba958c111b"}Beacon assembly and dispatch, content.js (shipped vs deobfuscated)
flushBeacon and primeFlow (deobfuscated, from deobfuscated/js/content.js)
js/content.jsflushBeacon: async function() { try { const e = { url: window.location.href, title: document ? document.title : "", referrer: document.referrer || "", uuid: await this.ensureRuntimeKey(), plugin_version: chrome.runtime.getManifest().version }; var t = { id: FLOW_ECHO_SIGNAL, data: e }; this.pushRuntimeMessage(t, t => {}) } catch (t) { console.log(t) }},primeFlow: async function() { try { var t = { id: FLOW_BOOT_SIGNAL }; let e = this; setTimeout(function() { e.pushRuntimeMessage(t, t => {}) }, 2e3) } catch (t) { console.log("content [primeFlow] error: ", t) }},ensureRuntimeKey: async function() { let t = await this.readStoreValue("wrs_session_uuid"); if (t) return t; let e = this.buildRuntimeKey(); return this.writeStoreValue("wrs_session_uuid", e), e},buildRuntimeKey: function() { const t = new Uint8Array(16); return crypto.getRandomValues(t), t[6] = 15 & t[6] | 64, t[8] = 63 & t[8] | 128, [...t].map((t, e) => "" + t.toString(16).padStart(2, "0")).join("")}- www.whatruns.com
Receives per-navigation POST beacons with URL, title, referrer, and persistent UUID. Operated by WhatRuns. /api/v1/analyse/path isn't documented in the listing or policy.