Is Click&Clean safe?
Click&Clean is medium risk. Click&Clean's service worker counts your browsing history and sends the count, timezone, language, and a fingerprint to api64.com on install and every 3 hours, with no opt-out. A capture showed count=72, timezone, and language on the wire.…
Who publishes itwww.hotcleaner.com - 1 other listing from the same operator, none carrying a finding
www.hotcleaner.com - 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, 400k+ users between them, none of them carrying a finding.
Shared hosts - 1 hostname
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
Click&Clean Sends Browsing-History Count and Fingerprint to api64.com
Click&Clean's service worker counts your browsing history and sends the count, timezone, language, and a fingerprint to api64.com on install and every 3 hours, with no opt-out.
A capture showed count=72, timezone, and language on the wire.
You install Click&Clean, or your browser starts up with it already installed.
The service worker automatically counts your entire browsing history, gathers fingerprint data, and sends it to api64.com without asking.
This repeats on a fixed timer, independent of anything else you do in the browser.
Fires once on install (or first run after this version was installed), then again roughly every 3 hours for as long as the browser keeps the extension's service worker active.
| Field | Value | Why it matters | |
|---|---|---|---|
Browsing history count | 72 | The number of pages Chrome has recorded in your history, up to 1,000. This is a count, not the URLs themselves. | |
Timezone offset | 0 | Your local timezone relative to UTC, used as a fingerprinting signal. | |
Browser language | en-US | Your browser's configured display language. | |
Browser brand/version fingerprint | Google Chrome 128.0.6613.84, Windows | Browser brand, full version, platform, and Chromium build number, read via User-Agent Client Hints and sent in the same request. |
esw901.js, Xa() builds the payload, dc() gates when it fires
// Xa() — builds the telemetry payload and sends the periodic check-in
async function sendCheckIn(extraFields) {
try {
const [brandIndex, fullVersionParts, platformCode, chromiumVersionParts] = await getBrowserFingerprint();
const historyResults = await chrome.history.search({ text: '', maxResults: 1000, startTime: 1 });
const now = Date.now();
const historyCount = historyResults?.length || 0;
const payload = {
// ...internal usage counters (a, b, c, d, r, s, t, u, v, w, x, y)...
o: new Date().getTimezoneOffset() / 60, // timezone offset in hours
p: navigator.language || '', // browser language
q: historyCount, // TOTAL BROWSING HISTORY COUNT
// ...browser brand/version/platform fingerprint fields (e, f, g, h, i, j, k, l, m, n)...
};
if (extraFields) Object.assign(payload, extraFields);
const [configResponse, httpStatus] = await fetchJson(
'https://api64.com/upd2',
{ method: 'POST', cache: 'no-store', body: JSON.stringify(payload), headers: { 'Content-Type': 'application/json' } },
'json'
);
if (httpStatus === 0) await applyRemoteConfig(configResponse);
return httpStatus || configResponse;
} catch (e) {
console.info(e);
return 5;
}
}
// dc() — gates sendCheckIn() to roughly once per 3-hour bucket
function checkThreeHourGate() {
const msSinceAnchor = Date.now() - 1606687200000; // fixed anchor timestamp
const bucket = msSinceAnchor < 10800000 ? 0 : Math.floor(msSinceAnchor / 10800000);
if (storedState.uf !== bucket) {
storedState.uf = bucket;
sendCheckIn(null); // fires on the first run (bucket 0, i.e. fresh install) and every ~3 hours after
}
}- api64.com
Receives history count, timezone, language, and device fingerprint from every install roughly every 3 hours, returning a remote-config payload the extension applies automatically.
api64.com Can Remotely Update Click&Clean's Domain Block List
When Click&Clean's check-in to api64.com succeeds, its response applies automatically, unconfirmed.
A capture added six hashed domains to the block list and set a region code.
Blocked domains, per source, get closed and purged from history.
api64.com responds to the extension's periodic check-in with a JSON configuration payload.
The check-in itself is described in the companion finding on the same endpoint.
The service worker applies the payload immediately: it can rewrite the domain block list, change internal flags, and clear cookies/cache for hotcleaner.com, without notifying you.
The extension trusts any HTTP 200 response body from this one endpoint.
| Field | Value | Why it matters | |
|---|---|---|---|
New domain block-list entries ('goa') | [372951892, -1323393189, -1200674118, -1371159776, 1891317919, 1120470599] | Six hashed values sent by the server are appended permanently to the extension's domain block list. | |
Default-site variant code ('dc') | 13 | Selects which region's 'default' sites the extension treats specially, set by the server, not shown to you. | |
Ready flag ('sf') | 1 | Tells the extension the response is valid config; the block-list fields above are only applied when this is present. |
esw901.js, cc() applies the server's response; ob()/nb() later enforce the block list it builds
// cc() — applies whatever the server sent, unconditionally, on HTTP 200
async function applyRemoteConfig(config) {
if ('sf' in config) {
if ('gg' in config) state.gg = config.gg; // "site guard" flag
if ('gf' in config) state.gf = config.gf; // "path guard" flag
if ('goc' in config) state.blockedOrigins.length = 0; // clear origin block list
if ('gpc' in config) state.blockedPaths.length = 0; // clear path block list
if ('gor' in config) removeEntries(state.blockedOrigins, config.gor);
if ('gpr' in config) removeEntries(state.blockedPaths, config.gpr);
if ('goa' in config) appendUnique(state.blockedOrigins, config.goa); // ADD hashed origins to block list
if ('gpa' in config) appendUnique(state.blockedPaths, config.gpa);
}
if ('dc' in config) state.defaultSiteVariant = config.dc; // selects region "default sites" set
if ('sm' in config) {
// Feature-flag branch — only reachable if the response ALSO carries 'sm'.
if ('mr' in config) state.mr = config.mr;
if ('ma' in config) state.ma = config.ma;
if ('ml' in config) state.ml = config.ml;
if ('mt' in config && state.mt !== config.mt) { /* toggle a history-visit listener */ }
}
if ('rtc' in config) await chrome.browsingData.remove({ origins: ['https://hotcleaner.com'] }, { cookies: true, cache: true, localStorage: true });
if ('op' in config) await setOptOutCookie(config.op); // sets an opt-out cookie on doubleclick.net
await persistState();
}// ob()/nb() — the block list built by applyRemoteConfig() is enforced here,
// on every browsing-history visit event (not independently verified in this
// dynamic-analysis session; shown from code review of the same file).
function checkAgainstBlockList(visitedUrl) {
const url = new URL(visitedUrl);
if (state.blockedOrigins.includes(hash(url.origin))) {
closeMatchingTabsAndForgetHistory(url.origin + '/');
} else if (state.blockedPaths.includes(hash(url.origin + url.pathname))) {
closeMatchingTabsAndForgetHistory(url.origin + url.pathname);
}
}
function closeMatchingTabsAndForgetHistory(urlPrefix) {
chrome.tabs.query({ url: urlPrefix + '*' }, (tabs) => {
if (tabs.length > 0) {
chrome.tabs.create({ url: 'chrome://newtab' });
for (const tab of tabs) {
chrome.tabs.remove(tab.id);
chrome.history.deleteUrl({ url: tab.url });
}
}
});
}The config-processing step runs synchronously whenever the api64.com/upd2 check-in succeeds, which itself fires on install and roughly every 3 hours the browser is active.
- api64.com
Its response to the check-in is applied to the domain block list and internal flags unconditionally on HTTP 200, no allowlist of safe fields, no on-screen disclosure.
What it can do
Permissions this extension asks for, as declared in version 9.8.2.0. Asking for a permission is not a finding on its own - it is what the extension can do if it chooses to.
Read and change your data on every site you visit
http://*/*
Read and change your data on every secure site you visit
https://*/*
See the address and title of every tab you have open
tabs
Clear your browsing history, cache and cookies
browsingData
Read and change your full browsing history
history
Read and change cookies, including the ones that keep you signed in
cookies
Run its own code inside the pages you visit
scripting
Store data in your browser
storage
Add items to the right-click menu
contextMenus