Is Speed Dial [FVD] - New Tab Page, 3D, Sync... safe?

High risk

FVD Speed Dial is high risk. Every new tab, FVD Speed Dial reads up to 10,000 history entries from the last 60 days, checking them against sponsored dials' target sites; sites visited 4+ times qualify for a tile. Observed live. Not disclosed in the Store listing.…

Apptitude LLCv81.8.35Chrome Web Store
75Risk
Who publishes it

APPTITUDE LLC - 3 other listings 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
Apptitude LLC
Declared legal entity
APPTITUDE LLC
Registered address
1467 Corsica Crest Ct, Las Vegas, NV 89123, US
Registered contact
Apptitude LLC

Same store account

3 other listings published from this account, 407k+ users between them, none of them carrying a finding.

Shared hosts - 8 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.

mediamarkt.ru
Also called by 3 other listings, including Live Start Page - Speed Dial
nimbus.everhelper.pro
Also called by 3 other listings, including EverSync - Sync bookmarks, backup your favorites.
s.nimbusweb.me
Also called by 3 other listings, including Nimbus Screen Capture: Screenshot, Edit, Annotate
search.fvdspeeddial.com
Also called by 3 other listings, including Live Start Page - Speed Dial
fvdspeeddial.com
Also called by 4 other listings, including Live Start Page - Speed Dial
packery.metafizzy.co
Also called by 5 other listings, including Kameleoon Debug Assistant
ulmart.ru
Also called by 5 other listings, including Live Start Page - Speed Dial
ww55.affinity.net
Also called by 5 other listings, including Norton Safe Search, Norton Home Page

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

Publishers can request a review.

Findings

New-Tab Extension Scans 60 Days of Browsing History to Pick Sponsored Tiles

Every new tab, FVD Speed Dial reads up to 10,000 history entries from the last 60 days, checking them against sponsored dials' target sites; sites visited 4+ times qualify for a tile.

Observed live.

Not disclosed in the Store listing.

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

You open a new tab.

The extension did this

The extension reads up to 10,000 browser history entries from the last 60 days and uses them, on-device, to decide which sponsored 'Recommended' tiles to show you.

We observed this during dynamic analysis: the extension's own console logging showed chrome.history.search firing with a 60-day lookback and returning real history entries right after the sponsored-dial catalog was fetched.

The code that does this

History is read, then matched against each dial's target host with a minimum-visits threshold:

Readable version

Reading up to 10,000 history entries from the last 60 days

js/bg/serverdialsModule.js
const history = {	// ...	search: function (startTime, query, cb) {		chrome.history.search(			{				text: query,				startTime: startTime,				maxResults: 10000,			},			function (items) {				debug.log('Search all items in the history for', query, 'found', items.length);				cb(null, items);			}		);	},};function HistorySearch() {	this.fetchHistoryFor = config.fetchHistoryFor;	this.historyData = '';}HistorySearch.prototype.getMinHistoryTime = function () {	return new Date().getTime() - this.fetchHistoryFor;};HistorySearch.prototype.init = function (cb) {	const self = this;	const minHistoryTime = this.getMinHistoryTime();	debug.log('Fetch history since', new Date(minHistoryTime));	history.search(minHistoryTime, '', function (err, items) {		if (err) {			return cb(err);		}		const lines = items.map(function (item) {			return item.url + '|' + item.visitCount;		});		debug.log('History data URLs count:', lines.length);		self.historyData = lines.join('\n').toLowerCase();		debug.log('History data size:', self.historyData.length / 1024 / 1024, 'MB');		cb(null);	});};

Matching each candidate dial's site against your local history (4+ visits qualifies)

js/bg/serverdialsModule.js
HistorySearch.prototype.getVisitsCountBySign = function (sign) {	const regExp = new RegExp(escapeStringRegexp(sign) + '[^\\|]+\\|([0-9]+)', 'ig');	let m;	let visits = 0;	do {		m = regExp.exec(this.historyData);		if (m) {			visits += parseInt(m[1]);		}	} while (m);	return visits;};HistorySearch.prototype.getChoices = function (dialsChoices, cb) {	const self = this;	const choices = [];	const startLookUpTime = new Date().getTime();	dialsChoices.forEach(function (dialChoice) {		if (!dialChoice.checkHost) {			return;		}		const visitsCount = self.getVisitsCountBySign(dialChoice.checkHost);		if (visitsCount >= config.minVisits) {			// this url was found in history, add dial for this url			debug.log('Host', dialChoice.checkHost, 'found in the history, add choice', dialChoice);			dialChoice.orderValue = visitsCount;			choices.push(dialChoice);		}	});	setTimeout(function () {		cb(null, choices);	});};
What the local history match reads and uses, per new-tab load
  • Pages you visited
    https://www.homedepot.com/s/patio+furniture|6

    Every history entry's URL, pulled from your browser history and matched against each sponsored dial's target site.

  • History window scanned
    Last 60 days

    How far back the extension looks in your browsing history on every scan.

  • Entries read per scan
    Up to 10,000 entries

    The maximum number of history rows pulled into memory each time the match runs.

  • Visit threshold for sponsored tile
    4 visits

    A dial's target site only becomes eligible to show as 'Sponsored' once you've visited it at least this many times in the window.

Captured request
GEThttps://geoloc.tempest.com/

Observed during dynamic analysis: this request returned IP-derived geolocation (country code, region, city) immediately after the sponsored-dial catalog was fetched. The response location is passed into the same dial-picking flow that reads your history, to filter which dials are eligible for your location.

Hosts contacted as part of the sponsored-dial flow that also reads your history
    • fvdspeeddial.com

    Serves the catalog of candidate sponsored/recommended dials for the new tab page. The request carries a userType parameter and, when set, a country parameter, not raw history.

    • geoloc.tempest.com

    Returns IP-derived geolocation (country, region, city) used to filter which sponsored dials are eligible for your location.

IP and city sent to geoloc.tempest.com on every new session

Two GET requests to https://geoloc.tempest.com/ fired on new session start, replying with country/city/lat/lng from your IP, stored in chrome.storage.session and attached to every Analytics event.

Not named in the Store listing.

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

You open the new tab page after 30 minutes of inactivity, starting a new session.

The extension did this

The extension fetches https://geoloc.tempest.com/ over your connection. tempest.com sees your IP and replies with country, city, and coordinates, stored and attached to analytics.

tempest.com is a separate company; it is not named in the extension's Chrome Web Store listing.

Captured request
GEThttps://geoloc.tempest.com/

200 OK, application/json: {"cc":"GB","CityName":"Brighton","lat":50.83,"lng":-0.19}

Headers
Host
geoloc.tempest.com
Accept
*/*
User-Agent
Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36
What tempest.com receives, and what it sends back
  • Your IP address
    82.46.0.0/16 (UK residential ISP)

    tempest.com sees the request's source IP, which is how it knows your country/city; the IP isn't a body field, it's the connection.

  • Country code
    GB

    Two-letter country code returned in the response and stored locally.

  • City name
    Brighton

    Best-guess city from the IP. Forwarded to Google Analytics on every later event.

  • Latitude / longitude
    50.83, -0.19

    Approximate coordinates derived from the IP, accurate to a city or neighbourhood. Stored in session memory.

The code that does this

The code that does this, from the extension's shipping source.

Readable version

The same code, lightly annotated

// js/bg/google-analytics.js — runs whenever a new session beginsasync getOrCreateSessionId() {  let { sessionData } = await chrome.storage.session.get('sessionData');  // ... if expired or missing, fall through to fetch ...  if (!sessionData) {    // Single GET to tempest.com — your IP is the lookup key.    const clientGeolocationResult = await fetch('https://geoloc.tempest.com');    const clientGeolocation = await clientGeolocationResult.json();    // {cc:'GB', CityName:'Brighton', lat:50.83, lng:-0.19}    sessionData = {      session_id: Date.now().toString(),      timestamp: Date.now().toString(),      geolocation: JSON.stringify(clientGeolocation),    };    await chrome.storage.session.set({ sessionData });  }  return sessionData.session_id;}
What's stored on your device

Held in session memory (cleared on Chrome quit) and re-attached to every Analytics event sent over the next 30 minutes of activity.

Location
chrome.storage.session, key 'sessionData'
Contents
{  "sessionData": {    "timestamp": "1744640329122",    "session_id": "1744640329122",    "geolocation": "{\"cc\":\"GB\",\"CityName\":\"Brighton\",\"lat\":50.83,\"lng\":-0.19}"  }}
Where your IP is exposed in this flow
    • geoloc.tempest.com

    Receives a GET each new session and dial-picker refresh; logs see your IP. Returns country, city, lat/lng. Owned by Tempest, not named in the Store listing.

    • www.google-analytics.com

    Receives the city and country (derived above) on every later GA4 event. See companion claim 'GA4 telemetry on install/update/dial click'.

Reproduce it yourself

Run in the Speed Dial [FVD] service worker DevTools console. Hooks fetch() so any call to https://geoloc.tempest.com is logged with timestamp and the JSON body returned. Confirms the lookup happens and shows you the exact data tempest.com handed back about your connection.

Requires
  • Chrome with Developer mode enabled
  • Speed Dial [FVD] installed
speed-dial-geoloc-watcher.js · js
// speed-dial-geoloc-watcher.js// Paste into the service worker DevTools console for Speed Dial [FVD].(function () {  const orig = self.fetch.bind(self);  self.fetch = async function (input, init) {    const url = typeof input === 'string' ? input : input.url;    const isGeo = url && url.includes('geoloc.tempest.com');    if (isGeo) {      console.log('[geoloc-watcher]', new Date().toISOString(), 'GET', url);    }    const res = await orig(input, init);    if (isGeo) {      try {        const clone = res.clone();        const body = await clone.text();        console.log('[geoloc-watcher] response body:', body);      } catch (e) {        console.warn('[geoloc-watcher] could not read body', e);      }    }    return res;  };  console.log('[geoloc-watcher] installed. Open a new tab after 30+ min idle, or run: chrome.storage.session.remove("sessionData")');})();
How to run it
  1. 1Visit chrome://extensions, enable Developer mode.
  2. 2Find 'Speed Dial [FVD]', click Service worker.
  3. 3Paste the script, press Enter.
  4. 4Clear sessionData, open a new tab.
  5. 5Watch console for the GET and JSON.
Updated 30 September 2026llaficoajjainaijghjlofdfmbjpebpa