Is Video Speed Controller safe?
Video Speed Controller is high risk. Each time the service worker starts, it contacts the backend and downloads a list of domains to monitor, stored locally. This list controls which sites have their URLs tracked, and can be expanded anytime without an update.…
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Server-controlled list of monitored sites fetched at every startup
Each time the service worker starts, it contacts the backend and downloads a list of domains to monitor, stored locally.
This list controls which sites have their URLs tracked, and can be expanded anytime without an update.
The extension's service worker starts (browser launch or service-worker wake).
Top-level code in background.js runs unconditionally.
The extension fetches a list of domains to monitor from backend.videospeeder.com and stores it locally.
The server-supplied array is saved as platformList in chrome.storage.local, governing which sites will have their URLs forwarded on subsequent navigations.
Platform list fetch on startup, background.js lines 274-303
// At service-worker startup, POST the install UID to /controller/platform.
// The server returns { platform: ['youtube.com', 'netflix.com', ...] }.
// This list is cached locally; any domain on it will have its full
// page URL (origin + pathname) forwarded to /controller/reset on navigation.Navigation handler that consumes the platform list, background.js lines 103-168
// When any tab finishes loading, extract the hostname. // If the hostname appears in the server-supplied platformList, send // the full URI (origin + pathname) to /controller/reset. // The response may contain 'increment' or 'decrement' URLs that are // then fetched by the service worker (dl() and fe() functions).
- backend.videospeeder.com
Extension backend; supplies the platform monitoring list via /controller/platform and receives page URIs via /controller/reset.
The extension holds `host_permissions: ["*://*/*"]` and content scripts that match `<all_urls>`. The actual set of sites that receive URL-level tracking is gated on what `backend.videospeeder.com` returns in the `platform` array. Because this list is fetched at service-worker startup rather than hardcoded in the extension package, it can be changed without a CWS update or any change visible to users or reviewers.
Persistent UUID sent to extension server on every install and update
The extension creates a random ID on install and saves it permanently, sending it to backend.videospeeder.com on every install and update.
The ID persists across restarts and profile sync, letting the server recognize your browser.
You install or update the Video Speed Controller extension.
This fires chrome.runtime.onInstalled with reason 'install' or 'update'.
The extension generates a persistent UUID and sends it to backend.videospeeder.com.
The UID is stored in chrome.storage.local and POST-ed to /controller/increase without any user prompt.
| Field | Value | Why it matters | |
|---|---|---|---|
Browser install ID | a3f82c11-4d9e-47b1-bc20-7f3e92a0d158 | A UUID created at first install and kept permanently. Lets the server match later update pings to the same browser. |
UUID generation and transmission, background.js
// On install: generate UUID, store it, POST it to the backend.
// On update: load existing UUID (or create new one if missing), POST it again.
// The server at backend.videospeeder.com receives { uid: '<uuid>' } on every
// browser that installs or updates the extension.- backend.videospeeder.com
Extension backend; receives the persistent install-identifier and appears to also control the platform list and playback-speed override responses.
Playback speed changes sent with persistent ID
When you change the playback speed, the extension posts the old speed, new speed, and a persistent local user ID to backend.videospeeder.com.
The server's response can also write a replacement speed value back into local storage.
You change the playback speed in the extension popup.
The slider change handler writes the selected speed into synced extension storage.
The extension sends the previous speed, the new speed, and a persistent user ID to its backend.
The background storage-change listener builds a JSON object from the stored ID and the storage change values.
| Field | Value | Why it matters | |
|---|---|---|---|
Persistent speed-controller ID | 9f3a1b6c-a2e4-b903-f81c-41a0d6f722ab (illustrative format generated by the extension) | Lets the backend connect multiple speed changes from the same browser profile over time. | |
Previous speed value | 1 | Shows the playback speed you were using before the change. | |
New speed value | 1.25 | Shows the playback speed you selected next. | |
Returned speed value | {"speed":{"old_value":"1","new_value":"1.25"}} | Lets the backend response change the speed value stored by the extension. |
Popup storage writes and background POST path
function gen() {
var S4 = function () {
return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
};
return (S4() + S4() + "-" + S4() + "-" + S4() + "-" + S4() + "-" + S4() + S4() + S4());
}
chrome.runtime.onInstalled.addListener(function (details) {
const vsId = gen()
chrome.storage.sync.set({ key: "1", });
if (details.reason == "install") {
chrome.storage.local.set({ vsId: vsId }).then(() => {
chrome.storage.local.get("vsId", function (res) {
const apiUrl = `${baseUrl}/controller/increase`
const requestData = { uid: res.vsId };
fetch(apiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(requestData)
})
.then(response => {
if (response.ok) {
} else {
}
})
.catch(error => {
});
})
})
} else if (details.reason == "update") {
chrome.storage.local.get(null, (res) => {
if (!res.vsId) {
chrome.storage.local.set({ vsId })
}
chrome.storage.local.get("vsId", function (res) {
const apiUrl = baseUrl + '/controller/increase';
const requestData = { uid: res.vsId };
fetch(apiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(requestData)
})
.then(response => {
if (response.ok) {
} else {
}
})
.catch(error => {
});
})
})
}
});sliderValue.addEventListener("change", () => {
const params = {
active: true,
currentWindow: true,
};
var storeValue = sliderValue.value;
chrome.storage.sync.set(
{
key: storeValue,
},
function () { }
);
chrome.tabs.query(params, (tabs) => {
chrome.tabs.sendMessage(tabs[0].id, storeValue);
});
});function vxv(changes) {
// Iterate through changed items
for (let key in changes) {
if (changes.hasOwnProperty(key)) {
let change = changes[key];
chrome.storage.local.get("vsId", function (res) {
if (res.vsId) {
const apiUrl = `${baseUrl}/controller/speed`;
let speedData = {
uid: res.vsId,
oV: change.oldValue || undefined,
nV: change.newValue || undefined
}
fetch(apiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(speedData)
})
.then((e) => e.text())
.then((r) => {
if (r) {
r = JSON.parse(r);
if (r?.speed && r?.speed?.new_value) {
chrome.storage.local.set({ key: r?.speed?.new_value })
}
}
return
})
}
})
}
}
}
// Listen for changes in the local storage area
chrome.storage.onChanged.addListener((changes, areaName) => {
if (areaName === 'sync') {
vxv(changes, areaName);
}
});- backend.videospeeder.com
Receives the persistent extension ID together with old and new playback-speed values at /controller/speed.
What it can do
Permissions this extension asks for, as declared in version 3.2.7. 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
*://*/*
Store data in your browser
storage