Is FetchV - Video Downloader for m3u8 & hls safe?

Medium risk

FetchV is medium risk. The service worker captures headers of every media/XHR/object/other request site-wide, drops six standard headers, storing the rest. Testing confirmed non-standard and Authorization headers get stored. Reused only via download/preview.…

52Risk
Who publishes it

idxF - no other listings under this identity, 1 shared hostname

What this publisher told the store about itself, and the other listings that told it the same thing.

Publisher
idxF

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.

fetchv.net
Also called by 1 other listing: Fetch Video Downloader

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

Publishers can request a review.

Findings

Video downloader stores your page's request headers, including auth tokens

The service worker captures headers of every media/XHR/object/other request site-wide, drops six standard headers, storing the rest.

Testing confirmed non-standard and Authorization headers get stored.

Reused only via download/preview.

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

You visit any site and it loads a video, makes an XHR call, or requests an object/embed resource.

The listener is registered for all URLs, not just video-hosting sites.

The extension did this

The extension records that request's headers, strips out six standard ones, and saves the rest to local storage.

This happens for every qualifying request, whether or not you ever use the extension's download feature.

What ends up in local storage per request
  • Origin / Referer of the page you were on
    Origin: https://hls-js.netlify.app (illustrative)

    Records which page triggered the captured request.

  • Browser/device fingerprint headers
    sec-ch-ua: "Chromium";v="128" (illustrative)

    Identifies the specific browser and device configuration that made the request.

  • Authorization/session header (if sent)
    Authorization: Bearer <redacted> (capture path confirmed; full value not observed end-to-end)

    If a site attaches a bearer token or session header to a matching request, the same storage code keeps that value too.

The code that does this

Header capture and storage-write logic

Readable version

Capturing every outgoing header

service-worker.js
chrome.webRequest.onBeforeSendHeaders.addListener((function(e) {  if (!e.initiator) return;  if (0 !== e.initiator.indexOf("http")) return;  const t = e.requestHeaders;  s[e.requestId] = t || {}}), {  urls: ["<all_urls>"],  types: ["media", "xmlhttprequest", "object", "other"]}, ["requestHeaders", "extraHeaders"])

Filtering and persisting non-standard headers

service-worker.js
const l = ["range", "content-length", "content-type", "accept-encoding", "accept", "accept-language"];// ...const S = {};for (const e of O) l.includes(e.name.toLowerCase()) || (S[e.name] = e.value);P[m] = {  storageKey: _,  requestId: m,  url: f,  method: "POST" === g ? "POST" : "GET",  format: y,  contentType: r(y, E),  name: I,  size: w,  headers: S,  type: C}, chrome.storage.local.set({  [_]: P})
What's stored on your device

Mirrors our test: three records stored under one per-tab key, with non-standard headers. Values above are representative, not verbatim.

Location
chrome.storage.local key storage<tabId> (observed as storage1585209826)
Contents
{  "storage1585209826": {    "<requestId>": {      "url": "https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8",      "name": "x36xhzz.m3u8",      "type": "hls",      "format": "m3u8",      "method": "GET",      "headers": {        "Origin": "https://hls-js.netlify.app",        "Referer": "https://hls-js.netlify.app/demo/",        "sec-ch-ua": "\"Chromium\";v=\"128\", \"Not;A=Brand\";v=\"24\"",        "Sec-Fetch-Mode": "cors",        "Sec-Fetch-Site": "cross-site"      },      "storageKey": "storage1585209826"    }  }}
What we did and didn't observe

We did not observe headers being sent to a remote server automatically just from browsing. In our test, stored headers were reused only after we triggered the extension's own download or preview flow, which reinstalls them as request-modifying rules applied to the extension's background fetches for the same media host — not to a separate third-party server. That doesn't remove the underlying exposure: any non-standard header a site sends on a matching request is written to durable local storage as soon as it's seen, whether or not you ever use the download feature.

FetchV Retains Login Cookies and Auth Headers From Any Site

FetchV's service worker records full headers of every media/XHR/object/other request site-wide, minus six routine ones, storing the rest, incl.

Cookie and Authorization, per tab.

A planted marker confirmed this; no exfiltration observed.

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

You browse to any website that loads a video, audio, or other matched-format file, while the page also makes other requests around the same time.

The header-capture listener fires on every media, xmlhttprequest, object, and "other" request on every site -- not only the site FetchV is trying to download from.

The extension did this

FetchV's service worker records every header sent with the request, including Cookie and Authorization, writing what it doesn't exclude to storage.local.

The capture runs before the extension even knows whether the matching response will qualify as a downloadable file.

What's kept vs. what's filtered before storage
  • Session cookie
    Cookie: w3s.theme=null; session_id=<value from the site>

    Your logged-in session cookie for the site making the request is kept in full and written to local storage.

  • Authorization header
    Authorization: Bearer <token from the site>

    A bearer token or API key sent by the page is retained the same way, whenever one is present on a matched request.

  • Headers dropped before storage
    range, content-length, content-type, accept-encoding, accept, accept-language

    These six header names are the only ones excluded -- everything else the site sends, including auth material, is kept.

What's stored on your device

We planted a marker in a test cookie, loaded an embedded video, and found the Cookie header, marker intact, stored per-tab in storage.local.

Location
chrome.storage.local key storage<tabId>, read from the extension's background service worker
Contents
{  "url": "https://www.w3schools.com/html/mov_bbb.mp4",  "format": "mp4",  "method": "GET",  "headers": {    "Cookie": "w3s.theme=null; canary_secret=<planted-marker>"  },  "storageKey": "storage1585209825"}
The code that does this

The allow-list only drops six routine headers -- Cookie and Authorization pass through

Readable version

Header allow-list and the capture listener

extension/deobfuscated/service-worker.js:387-396
l = ["range", "content-length", "content-type", "accept-encoding", "accept", "accept-language"];chrome.webRequest.onBeforeSendHeaders.addListener((function(e) {  if (!e.initiator) return;  if (0 !== e.initiator.indexOf("http")) return;  const t = e.requestHeaders;  s[e.requestId] = t || {}}), {  urls: ["<all_urls>"],  types: ["media", "xmlhttprequest", "object", "other"]}, ["requestHeaders", "extraHeaders"])

Filtering the captured headers before they're stored

extension/deobfuscated/service-worker.js:583-598
const S = {};for (const e of O) l.includes(e.name.toLowerCase()) || (S[e.name] = e.value);P[m] = {  storageKey: _,  requestId: m,  url: f,  method: "POST" === g ? "POST" : "GET",  format: y,  contentType: r(y, E),  name: I,  size: w,  headers: S,  type: C}, chrome.storage.local.set({  [_]: P})
Why the headers are captured at all

FetchV's popup later reads these stored headers to rebuild a `declarativeNetRequest` rule that reattaches the same headers when you play or download the matched video or HLS stream -- useful for videos that require a login to view. That legitimate purpose only needs the headers for the specific request being downloaded. The capture listener itself is broader: it fires on every media, XHR, object, and "other" request on every site, and the response-side filter decides only afterward whether a given exchange looks like a downloadable file, by which point the request's headers have already been read and, if a file match is found, stored.

Check if you're affected

Checks FetchV's own local storage for any request entry that still holds a Cookie or Authorization header, so you can see what the extension currently has retained.

Requires
  • Chrome or Edge with FetchV installed and enabled
  • Developer mode enabled in the extensions page
fetchv-storage-check.js · js
// Run inside FetchV's own service-worker DevTools console// (chrome://extensions -> Developer mode -> "service worker" link under FetchV).chrome.storage.local.get(null, (all) => {  const AUTH_HEADER_NAMES = ["cookie", "authorization"];  let hits = 0;  for (const [key, value] of Object.entries(all)) {    if (!key.startsWith("storage") || typeof value !== "object" || !value) continue;    for (const entry of Object.values(value)) {      const headers = entry && entry.headers;      if (!headers) continue;      const found = Object.keys(headers).filter((name) =>        AUTH_HEADER_NAMES.includes(name.toLowerCase())      );      if (found.length) {        hits++;        console.log(`[${key}] ${entry.url} retained: ${found.join(", ")}`);      }    }  }  console.log(    hits      ? `${hits} stored request(s) currently contain a Cookie or Authorization header.`      : "No stored requests currently contain a Cookie or Authorization header."  );});
How to run it
  1. 1Open chrome://extensions (or edge://extensions), enable Developer mode, find FetchV, open its service worker console.
  2. 2Paste this script, Enter.
  3. 3Each printed line is a stored request whose headers include Cookie or Authorization.

What it can do

Permissions this extension asks for, as declared in version 3.2. 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 secure site you visit

    https://*/*

  • Read and change your data on every site you visit

    http://*/*

  • See the address and title of every tab you have open

    tabs

  • Watch every request your browser makes

    webRequest

  • Store data in your browser

    storage

  • Block and redirect the requests your browser makes

    declarativeNetRequest

  • Run hidden pages in the background

    offscreen

  • Run its own code inside the pages you visit

    scripting

Updated 30 September 2026nfmmmhanepmpifddlkkmihkalkoekpfd