Is WhatRuns safe?

High risk

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.…

https://whatruns.comv1.10.0Chrome Web Store
75Risk

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

Publishers can request a review.

Findings

SeverityHIGH
ClassUNWANTED
TypeUnexpected
CWECWE-200
SourceAI SANDBOX

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.

01EvidenceCAUSE EFFECT
What actually happens
You did this

You navigate to any website in your browser.

The extension did this

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.

02EvidenceNETWORK CAPTURE
Captured request
POSThttps://www.whatruns.com/api/v1/get_site_apps
HTTP 200. Response includes technology fingerprint JSON and optional dom_data config object used to configure DOM scraping on subsequent page loads.
Headers
Content-Typeapplication/x-www-form-urlencoded
Body
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
03EvidenceFIELD TABLE
What WhatRuns sends on every page navigation:
FieldValueWhy 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.comThe exact hostname, including any subdomain, used to identify the specific site.
Root hostname
github.comThe 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)
YWJjMTIzZGVmNDU2Your WhatRuns API key, base64-encoded. Uniquely identifies your account on every request.
04EvidenceCODE COMPARE
The code that does this

The listener that fires on every navigation, from background.js

What it actually does
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))
  });
}
05EvidenceTHIRD PARTY LIST
Where your browsing data is sent:
  • 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.

SeverityMEDIUM
ClassUNWANTED
TypeUnexpected
CWECWE-200
SourceAI SANDBOX

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.

01EvidenceCAUSE EFFECT
What actually happens
You did this

You open a web page while WhatRuns is installed.

The content script runs on http and https pages at document idle.

The extension did this

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.

02EvidenceFIELD TABLE
Fields observed in the page-load POST
FieldValueWhy 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
3a4659a6dc0c468680cd66d0261bd6b0Lets repeated page reports from the same browser installation be linked together over time.
Extension version
1.8.20Identifies which WhatRuns build produced the report.
03EvidenceNETWORK CAPTURE
Captured request
POSThttps://www.whatruns.com/api/v1/collect_data
Body
{
  "url": "https://www.amazon.com/",
  "title": "Amazon.com. Spend less. Smile more.",
  "referrer": "https://www.amazon.com/",
  "uuid": "3a4659a6dc0c468680cd66d0261bd6b0",
  "plugin_version": "1.8.20"
}
04EvidenceCODE COMPARE
The code that does this

The shipped code builds the page record and posts it to WhatRuns

What it actually does
Content script waits two seconds, then sends page fieldsjs/content.js
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("")
}
Background worker serializes the data into a POST bodyjs/background.js
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
}
Constant resolving the current v1.10.0 destinationjs/global.constants.js
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";
05EvidenceTHIRD PARTY LIST
Network destination for the page record
  • www.whatruns.com

    Receives the page-load record containing URL, title, referrer, stored UUID, and extension version.

SeverityMEDIUM
ClassUNWANTED
TypeUnexpected
CWECWE-359
SourceAI SANDBOX

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.

01EvidenceCAUSE EFFECT
What actually happens
You did this

You visit any webpage.

The content script runs on every page due to the extension's <all_urls> match pattern.

The extension did this

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.

02EvidenceNETWORK CAPTURE
Captured request
POSThttps://www.whatruns.com/api/v1/analyse/path
HTTP 200. Same UUID observed across 7+ navigations to unrelated domains in our test session (observed during dynamic analysis).
Headers
Content-Typeapplication/json
Body
{
  "url": "https://github.com/torvalds/linux",
  "title": "torvalds/linux: Linux kernel source tree",
  "referrer": "https://github.com",
  "uuid": "d45bfbf41ccd49899dddebba958c111b",
  "plugin_version": "1.10.0"
}
03EvidenceFIELD TABLE
Fields transmitted in every beacon POST
FieldValueWhy it matters
Page URL
https://github.com/torvalds/linuxThe full address of every page you visit, including paths and query parameters.
Page title
torvalds/linux: Linux kernel source treeThe document title of the page, often revealing the topic or content you were viewing.
Referrer
https://github.comThe URL of the page you came from, revealing navigation patterns and sequences.
Persistent UUID
d45bfbf41ccd49899dddebba958c111bA 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.0The version of the WhatRuns extension installed.
04EvidenceSTORAGE DUMP
What's stored on your device

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.

Locationchrome.storage.local key 'wrs_session_uuid'
Contents (JSON)
{
  "wrs_session_uuid": "d45bfbf41ccd49899dddebba958c111b"
}
05EvidenceCODE COMPARE
The code that does this

Beacon assembly and dispatch, content.js (shipped vs deobfuscated)

What it actually does
flushBeacon and primeFlow (deobfuscated, from deobfuscated/js/content.js)js/content.js
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("")
}
06EvidenceTHIRD PARTY LIST
Beacon destination
  • 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.

Our write-ups

Updated 10 September 2026cmkdbmfndkfgebldhnkbfhlneefdaaip