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.…
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.
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.
| 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
| Field | Value | Why it matters | |
|---|---|---|---|
The site you are visiting | https://github.com/ | The full URL of the page you just loaded, including any query parameters. | |
Raw hostname | github.com | The exact hostname, including any subdomain, used to identify the specific site. | |
Root hostname | github.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
// 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
});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.
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.
| Field | Value | Why it matters | |
|---|---|---|---|
Current page URL | https://www.amazon.com/ | Shows the exact page you were viewing when the extension sent the request. | |
Page title | Amazon.com. Spend less. Smile more. | Adds readable context about the page you visited, even when the URL alone is not descriptive. | |
Referrer | https://www.amazon.com/ | Shows the page that led you to the current page when the browser provides one. | |
Stored UUID | 3a4659a6dc0c468680cd66d0261bd6b0 | Lets repeated page reports from the same browser installation be linked together over time. | |
Extension version | 1.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
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)
}
}
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("")
}forwardRuntimeEcho: 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
}export 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.
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.
| 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"
}| Field | Value | Why it matters | |
|---|---|---|---|
Page URL | https://github.com/torvalds/linux | The full address of every page you visit, including paths and query parameters. | |
Page title | torvalds/linux: Linux kernel source tree | The document title of the page, often revealing the topic or content you were viewing. | |
Referrer | https://github.com | The URL of the page you came from, revealing navigation patterns and sequences. | |
Persistent UUID | d45bfbf41ccd49899dddebba958c111b | A randomly generated ID stored permanently in your browser, sent on every page load, letting the server link your visits into one history. | |
Extension version | 1.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.
chrome.storage.local key 'wrs_session_uuid'{
"wrs_session_uuid": "d45bfbf41ccd49899dddebba958c111b"
}Beacon assembly and dispatch, content.js (shipped vs deobfuscated)
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)
}
},
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.