Is Video Speed Controller safe?

High risk

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

Video Speed Controllerv3.2.7Chrome Web Store
79Risk

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.

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

The extension's service worker starts (browser launch or service-worker wake).

Top-level code in background.js runs unconditionally.

The extension did this

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.

The code that does this

Platform list fetch on startup, background.js lines 274-303

Readable version
// 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.
The code that does this

Navigation handler that consumes the platform list, background.js lines 103-168

Readable version
// 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).
Network destinations
    • backend.videospeeder.com

    Extension backend; supplies the platform monitoring list via /controller/platform and receives page URIs via /controller/reset.

Scope is determined server-side at runtime

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.

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

You install or update the Video Speed Controller extension.

This fires chrome.runtime.onInstalled with reason 'install' or 'update'.

The extension did this

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.

Fields sent in the POST body to /controller/increase
  • 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.

The code that does this

UUID generation and transmission, background.js

Readable version
// 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.
Network destination
    • 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.

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

You change the playback speed in the extension popup.

The slider change handler writes the selected speed into synced extension storage.

The extension did this

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.

Fields the code places in the speed-change POST
  • 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.

Captured request
POSThttps://backend.videospeeder.com/controller/speed

Endpoint check returned {"speed":{"old_value":"1","new_value":"1.25"}} for uid/oV/nV parameters.

The code that does this

Popup storage writes and background POST path

Readable version

Persistent ID creation on install/update

background.js
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 => {          });      })    })  }});

Popup slider writes the selected speed

popup.js
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);  });});

Background listener posts old and new values

background.js
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 areachrome.storage.onChanged.addListener((changes, areaName) => {  if (areaName === 'sync') {    vxv(changes, areaName);  }});
External destination receiving the speed-change record
    • 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

Updated 21 September 2026gioehmkjkeamcinbdelehlpnpdcdjpdp