Is Speed Test safe?
OpenSpeedCheck is critical risk. Speed Test opens a Pusher connection via refresh.openspeedcheck.com, subscribing to per-user task channels. Tasks open a pinned Google tab, search given keywords, click by probability, follow deep links. Code-backed, not packet-captured.…
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Remote task server can drive Google search automation
Speed Test opens a Pusher connection via refresh.openspeedcheck.com, subscribing to per-user task channels.
Tasks open a pinned Google tab, search given keywords, click by probability, follow deep links.
Code-backed, not packet-captured.
- Severity
- Critical unwanted
- Type
- Unexpected
- CWE
- CWE-940
- Source
- Dynamic sandbox
You open Google Trends while Speed Test is installed.
The manifest injects the Speed Test content script on trends.google.com pages.
The extension joins private task channels and can run remote-provided Google search and click tasks.
The task fields are forwarded from the content script to the background worker and then into the Google-page automation script.
- Your extension user IDa8f3d21c9b70-openspeedcheck (illustrative format)
This lets the remote service send tasks to a channel tied to your browser profile.
- Extension version and sourceversion 1.3.10, source speedtest, new UI true
This tells the service which Speed Test build and UI mode are active in your browser.
- Search keywordTop Entertainment & Media Trends of 2025
This is the phrase the extension can enter into Google on your device.
- Target URLhttps://meetglimpse.com/trends/entertainment-media-trends/
This is the destination the extension compares against search results before deciding what to click.
- Click rulesprobClick 0.6, timeOnPageAvg 30, shouldDeepClick false
These values control how likely the extension is to click a result and how long it waits on pages.
- Navigation history for the taskclick on result 3 from Google page 1 (illustrative format)
This records what the automation has already clicked so later task steps can avoid or repeat pages.
The request body and response were not captured during dynamic analysis.
Pusher channels receive task events and forward them to the background worker
Readable content script connection and event binding
content-scripts/speedtest.jsasync function i() { var _; const c = await Hi(); if (!Ge.value || !c || !er()) return; (_ = e.value) == null || _.disconnect(), e.value = new Yc("9c76e82799d0b07e4f22", { authEndpoint: "https://refresh.openspeedcheck.com/api/seasonality_data/pandora/user/auth", cluster: "mt1" }); const h = e.value.subscribe(`private-channel-${Ge.value}`), d = e.value.subscribe(`private-search-tasks-${Ge.value}`); d.bind("pusher:subscription_succeeded", () => { d.bind("create-task", async m => { await Se.runtime.sendMessage({ type: "upsert-search-task", data: m }) }), d.bind("create-tasks-batch", async m => { await Se.runtime.sendMessage({ type: "upsert-batch-search-tasks", data: m }) }), d.bind("update-task", async m => { await Se.runtime.sendMessage({ type: "upsert-search-task", data: m }) }), d.bind("cancel-task", async m => { await Se.runtime.sendMessage({ type: "cancel-search-task", data: m }) }) }), h.bind("pusher:subscription_succeeded", () => { s(), h.bind("task", async m => { try { const S = er() === "new", { data: E, user_type: w } = await Zc(m, S); await zc({ task_id: m.task_id, data: E, user_type: w }, S) } catch (S) { try { await Bc(m, S == null ? void 0 : S.message) } catch {} } }), h.bind("terminate", () => { l() }) })}Background and page scripts turn remote task fields into Google searches and clicks
Readable background worker starts a task in the pinned Google tab
background.jsasync startTask(e) { try { const r = await ve(); if (r === null) throw new Error("No leader tab found"); this.activeTasks.set(e.taskId, r), this.attachTabListener(r, e), await y.tabs.update(r, { url: "https://www.google.com/", active: !1 }) } catch (r) { throw r }}async onTabReady(e, r) { try { try { await Promise.race([y.tabs.sendMessage(e, { type: "check-if-has-simulator-script" }), new Promise((s, a) => Zt(a, 500))]), await y.tabs.sendMessage(e, { type: "restart-speedtest-simulation", task: { taskId: r.taskId, keyword: r.keyword, targetUrl: r.targetUrl, probClick: r.probClick ?? .8, probReclick: r.probReclick ?? .3, timeOnPageMultiple: r.timeOnPageMultiple ?? 1, timeOnPageAvg: r.timeOnPageAvg ?? 30, shouldDeepClick: r.shouldDeepClick ?? !1, probabilityNextPageOnMiss: r.probabilityNextPageOnMiss ?? 0, deepclick: r.deepclick, navigationHistory: this.taskHistory.get(r.taskId) || [], currentPage: this.taskPagesViewed.get(r.taskId) || 0, clicksCounter: this.taskClicksCounters.get(r.taskId) || 0, targetPageRankRecorded: !!this.taskTargetPageRank.get(r.taskId) } }) } catch { await Promise.all([y.scripting.executeScript({ target: { tabId: e }, files: ["ui_google.js"] }), y.scripting.executeScript({ target: { tabId: e }, files: ["speedtest_overlay.js"] })]), await y.tabs.sendMessage(e, { type: "start-speedtest-simulation", task: { taskId: r.taskId, keyword: r.keyword, targetUrl: r.targetUrl, probClick: r.probClick ?? .8, probReclick: r.probReclick ?? .3, timeOnPageMultiple: r.timeOnPageMultiple ?? 1, timeOnPageAvg: r.timeOnPageAvg ?? 30, shouldDeepClick: r.shouldDeepClick ?? !1, probabilityNextPageOnMiss: r.probabilityNextPageOnMiss ?? 0, deepclick: r.deepclick, navigationHistory: this.taskHistory.get(r.taskId) || [], currentPage: this.taskPagesViewed.get(r.taskId) || 0, clicksCounter: this.taskClicksCounters.get(r.taskId) || 0, targetPageRankRecorded: !!this.taskTargetPageRank.get(r.taskId) } }) } } catch (s) { throw s }}Readable Google-page automation chooses and clicks results
ui_google.jsif (!i || !i.keyword) throw new Error("Invalid task data");r = i, m = !0;try { await b("in-progress"); const a = M(); if ((!u || u.length === 0) && (a == null ? void 0 : a.type) !== "to_referrer") { await x(); return } if (!a) return await Promise.all([C({ type: "initial_search", data: { searchQuery: r.keyword } }), F(1)]), B(r.keyword); if (a.type === "initial_search" || a.type === "back" || a.type === "next_page") { a.type === "back" && await v(4e3); const l = N(); if ((a.type === "initial_search" || a.type === "next_page") && await z(l), l.length === 0) throw new Error("No search results found on the page"); const c = new Set(r.navigationHistory.filter(o => { var h, k; return o.type === "click" && ((h = o.data) == null ? void 0 : h.resultIndex) !== void 0 && ((k = o.data) == null ? void 0 : k.searchPage) === r.currentPage }).map(o => o.data.resultIndex)), g = l.findIndex(o => { try { return o.href && $(o.href).includes($(r.targetUrl)) } catch { return !1 } }); !r.targetPageRankRecorded && g !== -1 && await D(10 * (r.currentPage - 1) + g + 1); const s = await U({ results: l, targetIndex: g, visitedIndices: c, probClick: r.probClick }); if (s === void 0 || r.clicksCounter >= f && g === -1) return g !== -1 || !await j() ? x() : S(); const d = l[s]; d.scrollIntoView({ behavior: "smooth", block: "center" }), await v(A(500, 1500)), await Promise.all([C({ type: "click", url: d.href, data: { resultIndex: s, targetIndex: g, searchPage: r.currentPage } }), g === -1 ? I(r.clicksCounter + 1) : Promise.resolve()]), d.click(); return }} catch (a) { const l = a instanceof Error ? a.message : "Unknown error"; throw await b("failed", l), a}- refresh.openspeedcheck.com
Authenticates private Pusher channels and provides reranker configuration and task result endpoints for the Speed Test workflow.
- ws.pusherapp.com
Pusher WebSocket service used by the bundled Pusher client for private task-channel delivery.
- www.google.com
The extension opens or reuses a Google tab where the supplied keyword searches and result clicks are performed.
- trends.google.com
The trigger page where the Speed Test content script starts the task-channel connection.
Speed Test fetches remote click-behavior config every 3 minutes
The extension polls a remote server every 3 minutes for config controlling simulated search-engine clicks: positions to click, dwell time, and deep-link URLs to follow.
Settings apply immediately, so behavior can change without an update.
- Severity
- High unwanted
- Type
- Unexpected
- CWE
- CWE-829
- Source
- Dynamic sandbox
Every three minutes after install or browser startup, the extension's background service worker fires a scheduled alarm.
The alarm is named FETCH_CONFIG and is created with periodInMinutes: 3 both on install and on startup.
The background script fetches a remote JSON configuration that controls how clicks are simulated in your browser.
The response is stored in chrome.storage.local and immediately governs which search-result positions get clicked, how long the browser lingers on each page, and which deep-link URLs are followed.
JSON object containing click_config (array of click probability + timing entries), deepclick (array of {name, url, click_likelihood} objects), and pin_tab_config (pinned tab behavior flags). Confirmed during dynamic analysis with HTTP 200 returned in both test sessions.
- Content-Type
- application/json
Holds remote-controlled click-simulation parameters, read on each results page to decide what to click and how long to stay.
- Location
- chrome.storage.local
{ "CLICKS_CONFIG": [ { "time_multiplier": { "max": 1.5, "min": 0.8 }, "click_probability": 0.6, "reclick_probability": { "max": 0.3, "min": 0.1 } } ], "PINNED_TAB_CONFIG": { "speedtest": true }, "DEEP_CLICKS_CONFIG": [ { "url": "/contact", "name": "Law", "click_likelihood": 0.7 } ]}Every 3 minutes
Config is refreshed every 3 minutes on both install and startup. An additional fetch fires immediately on extension load (background.js:2893, a() call outside the alarm listener).
Config fetch and alarm setup, shipped vs deobfuscated
Config GET function (deobfuscated)
background.js (line 1273)function $t() { return We.get("pandora/reranker/config").json()}Alarm creation and storage write (deobfuscated)
background.js (lines 2844-2919)y.alarms.create(e, { periodInMinutes: 3 // FETCH_CONFIG alarm}),y.alarms.create(r, { periodInMinutes: 1 // PING_LEADER alarm})...async function a() { try { const i = await $t(); i.click_config.sort((d, _) => d.position - _.position); const g = i.click_config.map(d => ({ click_probability: d.prob_click, reclick_probability: { min: d.prob_reclick[0], max: d.prob_reclick[1] }, time_multiplier: { min: d.time_on_page_multiple[0], max: d.time_on_page_multiple[1] } })), m = i.deepclick.map(d => ({ name: d.name, url: d.url.replace(/\/$/, '').toLowerCase(), click_likelihood: d.click_likelihood })); await y.storage.local.set({ CLICKS_CONFIG: g, DEEP_CLICKS_CONFIG: m, PINNED_TAB_CONFIG: i.pin_tab_config }) } catch (i) { console.error('Error fetching positions config:', i) }}- refresh.openspeedcheck.com
Operator config server, subdomain of openspeedcheck.com. GET every 3 minutes returns JSON controlling click probability, timing, deep-link targets, and pinned-tab behavior.
What it can do
Permissions this extension asks for, as declared in version 1.3.10. 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
<all_urls>
Run its own code inside the pages you visit
scripting
Store data in your browser
storage
See the address and title of every tab you have open
tabs
Schedule its own background tasks
alarms