Is Video Downloader - Download M3U8, MP4, HLS and Live Streams safe?

High risk

Qooly is high risk. Every install fetches network filtering rules from qooly.com via declarativeNetRequest, able to add/modify/remove headers (cookie, referer, origin) on any request, any site. 8 rules applied at startup; the server can change rules anytime.…

75Risk
Who publishes it

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

Publisher
qooly

Same store account

1 other listing published from this account, 30k+ users between them, none of them carrying a finding.

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

Publishers can request a review.

Findings

Remote Server Controls Your Browser's Network Rules

Every install fetches network filtering rules from qooly.com via declarativeNetRequest, able to add/modify/remove headers (cookie, referer, origin) on any request, any site. 8 rules applied at startup; the server can change rules anytime.

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

You install the extension or restart your browser.

The extension did this

The extension fetches a list of network rules from qooly.com and applies them to your browser.

These rules can rewrite or remove HTTP headers on any request you make to any website, without any visible indication.

Captured request
GEThttps://qooly.com/domains-list.json?v=1

JSON array of 8 declarativeNetRequest rules including modifyHeaders actions on cookie, referer, origin, sec-fetch-dest, sec-fetch-mode, and sec-fetch-site headers across matched URL patterns.

The code that does this

The code that fetches and applies the remote rules

Readable version

The fetch action

// Runs on startup (and every 24 hours thereafter).// xt.a === 'qooly.com' (hardcoded constant)actions.UPDATE_RULES = function(store) {  const lastFetch = store.state.storage.remoteConfig.rulesFetchDate;  const stale = !lastFetch || lastFetch + 86_400_000 < Date.now(); // 24h  if (!stale) return;  fetch('https://qooly.com/domains-list.json?v=1')    .then(r => r.json())    .then(rules => {      if (rules && rules.length) {        store.commit('SET_RULES', rules);                // writes to chrome.storage.local        store.commit('SET_RULES_FETCH_DATE', Date.now());      }    });};

The rule application — fires whenever rules are updated in storage

// Called when Vuex emits 'remoteConfig/SET_RULES'class DynamicRules {  setRules() {    chrome.declarativeNetRequest.getDynamicRules().then(existingRules => {      chrome.declarativeNetRequest.updateDynamicRules({        addRules:      store.state.storage.remoteConfig.rules, // server's list        removeRuleIds: existingRules.map(r => r.id)           // clears prior rules first      });    });  }}
Where the network rules come from
    • qooly.com

    Developer domain for Video Downloader. Serves both the DNR rule list (/domains-list.json) and the M3U8 domain allow-list (/master-list.json). Also hosts the uninstall survey page.

Check if you're affected

Reads the current dynamic declarativeNetRequest rules applied by Video Downloader and prints them to the console. Run this in the extension's service worker DevTools to see exactly what rules are currently active.

Requires
  • Chrome with Developer mode enabled
  • Video Downloader extension installed
check-dnr-rules.js · js
// check-dnr-rules.js// Reads the active declarativeNetRequest dynamic rules set by Video Downloader.// Run this snippet in the service worker DevTools console for this extension.chrome.declarativeNetRequest.getDynamicRules().then(rules => {  if (rules.length === 0) {    console.log('[DNR-CHECK] No dynamic rules are currently active.');    return;  }  console.log(`[DNR-CHECK] ${rules.length} dynamic rule(s) are currently active:`);  rules.forEach((rule, i) => {    console.log(`\nRule ${i + 1} (id=${rule.id}):`);    console.log('  condition:', JSON.stringify(rule.condition, null, 2));    console.log('  action:', JSON.stringify(rule.action, null, 2));  });});// To also see what qooly.com is currently serving:fetch('https://qooly.com/domains-list.json?v=1')  .then(r => r.json())  .then(rules => {    console.log('\n[DNR-CHECK] Current server-side rules from qooly.com/domains-list.json:');    console.log(JSON.stringify(rules, null, 2));  });
How to run it
  1. 1Open chrome://extensions, enable Developer mode.
  2. 2Find the extension, click 'service worker' to open DevTools.
  3. 3Paste this script, press Enter.
  4. 4Output shows active rules and what qooly.com is serving.

Remote Server Decides Which Streaming Sites the Extension Intercepts

The extension fetches a streaming-domain list from qooly.com at startup, deciding which HLS/M3U8 streams to intercept.

The list includes 'twimg.com' and 'hls.ted.com', stored locally.

The server can change it anytime, no update needed.

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

You install the extension or restart your browser.

The extension did this

The extension fetches a list of streaming domains from qooly.com and uses it to decide which video streams to intercept.

The server controls which sites' HLS streams the extension processes, and can change that list remotely without any extension update.

Captured request
GEThttps://qooly.com/master-list.json

JSON object with field 'master-m3u8-domains' containing an array of streaming domain hostnames. Observed response: {"master-m3u8-domains": ["twimg.com", "hls.ted.com"]}. Extension storage confirmed rawMasterDomains set to ["twimg.com", "hls.ted.com"] matching the server response exactly.

What's stored on your device

The extension saved the server's domain list locally. The timestamp (2026-04-15T07:29:00 UTC) confirms it was set at session start.

Location
chrome.storage.local key '@@vwe-persistence', field remoteConfig.rawMasterDomains
Contents
{  "rawMasterDomains": [    "twimg.com",    "hls.ted.com"  ],  "rawMasterDomainsFetchDate": 1776238140419}
The code that does this

The code that fetches and applies the remote domain list

Readable version

The fetch action

// Runs on startup (and every 24 hours thereafter).// xt.a === 'qooly.com' (hardcoded constant)actions.UPDATE_RAW_MASTER_DOMAINS = function(store) {  const lastFetch = store.state.storage.remoteConfig.rawMasterDomainsFetchDate;  const stale = !lastFetch || lastFetch + 86_400_000 < Date.now(); // 24h  if (!stale) return;  fetch('https://qooly.com/master-list.json')    .then(r => r.json())    .then(data => {      const domains = data && data['master-m3u8-domains']        ? data['master-m3u8-domains']        : [];      store.commit('SET_RAW_MASTER_DOMAINS', domains);  // writes to chrome.storage.local      store.commit('SET_RAW_MASTER_DOMAINS_FETCH_DATE', Date.now());    });};

The intercept filter — checks each intercepted response URL against the server's list

// bg.js:7202 — inside the webRequest onHeadersReceived handlerconst masterDomains = store.state.storage.remoteConfig.rawMasterDomains;let domainMatch = false;for (let i = 0; i < masterDomains?.length; i++) {  if (request.url.includes(masterDomains[i])) {    domainMatch = true;  // this request's URL is on the server-supplied list  }}// Only process this response as an HLS stream if domainMatch is true// (or if the URL ends in .m3u8 / content-type matches)
Where the domain allow-list comes from
    • qooly.com

    Developer domain for Video Downloader. Serves the M3U8 allow-list (/master-list.json) and DNR rule list (/domains-list.json). Observed returning twimg.com and hls.ted.com.

Updated 30 September 2026lfdconleibeikjpklmlahaihpnkpmlch