Is Video Downloader PLUS safe?

High risk

Video Downloader PLUS is high risk. Our dynamic analysis observed the worker POST to videodetect.fdown.net on every tab activation or page load on Wikipedia, YouTube, Reddit and Amazon, carrying the page URL, referrer, and a tracking ID; fires on any page, not just video.…

FDOWNv6.5.5Chrome Web Store
75Risk
Who publishes it

FDOWN - no other listings under this identity

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

Publisher
FDOWN

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

Publishers can request a review.

Findings

SeverityHIGH
ClassUNWANTED
TypeUnexpected
CWECWE-359
SourceAI SANDBOX

Video Downloader PLUS reports every page you visit to fdown.net

Our dynamic analysis observed the worker POST to videodetect.fdown.net on every tab activation or page load on Wikipedia, YouTube, Reddit and Amazon, carrying the page URL, referrer, and a tracking ID; fires on any page, not just video.

01EvidenceCAUSE EFFECT
What actually happens
You did this

You switch to a tab or a page finishes loading, on any website.

This includes ordinary browsing, not just video pages or use of the download feature.

The extension did this

The extension's background service worker sends the exact page URL, its referrer, and a permanent tracking ID to fdown.net's server.

The request fires immediately, before you interact with the extension in any way.

02EvidenceFIELD TABLE
Fields sent to videodetect.fdown.net on every navigation
FieldValueWhy it matters
Page you're viewing
https://en.wikipedia.org/wiki/Web_browserThe exact address of the page you just opened or switched to, on any website.
Referring page
https://en.wikipedia.org/wiki/InternetThe page that linked you to the page you're viewing.
Permanent tracking ID
8554ac37-fb10-4e47-b814-d520f0dfccacA random ID generated once and stored permanently, letting fdown.net link together everything you browse across every session.
03EvidenceNETWORK CAPTURE
Captured request
POSThttps://videodetect.fdown.net/api/videoplatform/
Observed during dynamic analysis: this exact request shape fired immediately after each navigation, with a new targetUrl each time, repeating across every subsequent navigation in the same session.
Headers
Content-Typeapplication/json
Body
{
  "targetUrl": "https://en.wikipedia.org/wiki/Web_browser",
  "referrerUrl": "https://en.wikipedia.org/wiki/Internet",
  "userId": "8554ac37-fb10-4e47-b814-d520f0dfccac"
}
04EvidenceCODE COMPARE
The code that does this

The VideoPlatformDetect class registers navigation listeners and posts every visited URL

What it actually does
Readable VideoPlatformDetect classscripts/background.build.min.js
class VideoPlatformDetect {
  config = {
    url: "https://videodetect.fdown.net/api/videoplatform/"
  };
  videoPlatformDetectInfo = {};
  uuid = null;
  async run() {
    chrome.runtime.onMessage.addListener(async t => {
      "requestVideoPlatformDetect" === t.subject && this.sendVideoPlatformDetectMessage(t.tabId)
    }), chrome.tabs.query({
      active: !0,
      currentWindow: !0
    }, t => {
      this.fetchTabs(null, t)
    }), chrome.tabs.onActivated.addListener(e => {
      chrome.tabs.query({
        active: !0,
        currentWindow: !0
      }, t => {
        this.fetchTabs(e.tabId, t)
      })
    }), chrome.tabs.onUpdated.addListener(e => {
      chrome.tabs.query({
        active: !0,
        currentWindow: !0
      }, t => {
        this.fetchTabs(e, t)
      })
    }), this.uuid = await this.getUuid()
  }
  fetchTabs(t, e) {
    for (var o of e) null !== t && o.id !== t || this.fetchInfo(o)
  }
  async fetchInfo(t) {
    var e = t.url,
      o = t.id;
    if (!this.videoPlatformDetectInfo[o] || this.videoPlatformDetectInfo[o].url !== e)
      if (this.videoPlatformDetectInfo[o] = {
          score: null,
          url: e
        }, this.isValidUrl(e)) try {
        var n = await this.getReferrer(t),
          {
            score: r,
            cause: i
          } = await (await fetch(this.config.url, {
            method: "POST",
            cache: "no-cache",
            headers: {
              "Content-Type": "application/json"
            },
            body: JSON.stringify({
              targetUrl: e,
              referrerUrl: n,
              userId: this.uuid
            })
          })).json();
        this.videoPlatformDetectInfo[o] = {
          score: r,
          cause: i,
          url: e
        }, this.sendVideoPlatformDetectMessage(o)
      } catch (t) {
        this.videoPlatformDetectInfo[o] = void 0
      } else this.sendVideoPlatformDetectMessage(o)
  }
  async getUuid() {
    const e = await new Promise(e => {
      chrome.storage.sync.get(["uuid"], t => {
        e(t.uuid)
      })
    });
    if (e && this.validateUUID4(e)) return e;
    {
      const e = this.makeUUID();
      return new Promise(t => {
        chrome.storage.sync.set({
          uuid: e
        }, async () => {
          t(e)
        })
      })
    }
  }
  makeUUID() {
    return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (t, e) => ("x" === t ? 16 * Math.random() | 0 : 3 & e | 8).toString(16))
  }
  validateUUID4(t) {
    return new RegExp(/^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i).test(t)
  }
  isValidUrl(t) {
    if ("about:blank" === t || t.startsWith("chrome://")) return !1;
    try {
      return new URL(t), !0
    } catch (t) {
      return !1
    }
  }
  sendVideoPlatformDetectMessage(t) {
    chrome.runtime.sendMessage({
      subject: "videoPlatformDetect",
      data: this.videoPlatformDetectInfo[t]
    })
  }
  getReferrer(t) {
    return new Promise(e => {
      chrome.scripting.executeScript({
        target: {
          tabId: t.id
        },
        func: function() {
          return document.referrer
        }
      }).then(t => {
        e(t[0].result)
      })
    })
  }
}
Readable startup callscripts/background.build.min.js
const videoPlatformDetect = new VideoPlatformDetect;
window.videoPlatformDetect = window.videoPlatformDetect || videoPlatformDetect, (async () => {
  fbdExtensionApp.Media.init(), fbdExtensionApp.MainButton.refreshMainButtonStatus(), chrome.runtime.setUninstallURL("https://fdown.net/ext/uninstall.php"), chrome.tabs.query({
    active: !0,
    currentWindow: !0
  }, function(t) {
    0 < t.length && set_popup(t[0].id)
  }), chrome.storage.local.get(["fbdown-uuid"], function(t) {
    Object.keys(t).length || (t = uuid.v4(), chrome.storage.sync.set({
      "fbdown-uuid": t
    }, async function() {}))
  }), await videoPlatformDetect.run(), fbdExtensionApp.Utils.setUUID(videoPlatformDetect.uuid)
})(), chrome.tabs.onUpdated.addListener(function(t, e, o) {
  "complete" == o.status && set_popup(t)
}), chrome.tabs.onActivated.addListener(function(t) {
  set_popup(t.tabId)
});
05EvidencePLAIN NOTE
The consent flag we found gates a different feature, not this one

The extension does have a stored consent flag (`share_stats_consentment`) and a helper that checks it, but that check only gates whether the popup rebuilds its list of detected downloads when you open it. The navigation listeners that send your page URL and referrer to videodetect.fdown.net are started unconditionally at browser startup, before any consent check runs and regardless of that flag's value.

06EvidenceTHIRD PARTY LIST
Where the data ends up
  • videodetect.fdown.net

    Receives the current tab URL, referrer, and persistent tracking ID on every navigation; operated by fdown.net, the extension's own developer.

SeverityMEDIUM
ClassUNWANTED
TypeUnexpected
CWECWE-359
SourceAI SANDBOX

Consent Choices Sent With Persistent UUID

When you choose a consent option in Video Downloader PLUS, the popup sends it to the worker, which reads its UUID and posts the value to consentmangement.fdown.net, the same UUID sent as userId in the video-detection request.

01EvidenceCAUSE EFFECT
What actually happens
You did this

You make a consent selection in the extension popup.

The popup has separate buttons for agreeing, accepting all, and confirming the custom setting.

The extension did this

The extension sends your selected consent value with a stored UUID.

That UUID is the same stored identifier used in another background request.

02EvidenceFIELD TABLE
Fields prepared for the consent request
FieldValueWhy it matters
Your consent answer
trueThis records whether you accepted sharing in the extension's consent prompt.
Your extension UUID
8b8b0d10-4c7c-4fd6-a62f-6f328a04b68d (illustrative)This lets the consent record be tied to the same browser profile over time.
03EvidenceNETWORK CAPTURE
Captured request
POSThttps://consentmangement.fdown.net/api/consent
The source-confirmed request target is shown here; dynamic analysis did not record a consent POST response for this trigger.
Headers
Content-Typeapplication/json
04EvidenceCODE COMPARE
The code that does this

The popup sends the selected consent value to the background worker

What it actually does
Readable equivalent of the popup consent pathscripts/popup.build.min.js
function sendConsentChoice(value) {
  chrome.runtime.sendMessage({ subject: "SaveConsentChoice", value: value }, function(response) {
    response.status;
  });
}

function showConsentDialog() {
  var dialog = document.getElementById("disclaimer_message");
  if (dialog) dialog.classList.remove("d-none");

  var agree = document.getElementById("dismiss-disclaimer-agree");
  if (agree) {
    agree.addEventListener("click", function(event) {
      event.preventDefault();
      fbdExtensionApp.Utils.setShareStatsConsentmentValue(true);
      sendConsentChoice(true);
      var dialog = document.getElementById("disclaimer_message");
      if (dialog) dialog.classList.add("d-none");
    }, false);
  }

  var customize = document.getElementById("dismiss-disclaimer-customize");
  if (customize) {
    customize.addEventListener("click", function(event) {
      var dialog = document.getElementById("disclaimer_message");
      if (dialog) dialog.classList.add("d-none");

      var customizeScreen = document.getElementById("customize_screen");
      if (customizeScreen) customizeScreen.classList.remove("d-none");

      var accept = document.getElementById("dismiss-disclaimer-accept");
      if (accept) {
        accept.addEventListener("click", function(event) {
          event.preventDefault();
          fbdExtensionApp.Utils.setShareStatsConsentmentValue(true);
          sendConsentChoice(true);
          if (customizeScreen) customizeScreen.classList.add("d-none");
        }, false);
      }

      var confirm = document.getElementById("dismiss-disclaimer-confirm");
      if (confirm) {
        confirm.addEventListener("click", function(event) {
          event.preventDefault();
          if (document.getElementById("URLs").checked) {
            fbdExtensionApp.Utils.setShareStatsConsentmentValue(true);
            sendConsentChoice(true);
          } else {
            fbdExtensionApp.Utils.setShareStatsConsentmentValue(false);
            sendConsentChoice(false);
          }
          if (customizeScreen) customizeScreen.classList.add("d-none");
        }, false);
      }

      var collapsibles = document.getElementsByClassName("collapsible");
      for (var index = 0; index < collapsibles.length; index++) {
        collapsibles[index].addEventListener("click", function() {
          this.classList.toggle("active");
          var content = this.nextElementSibling;
          content.style.maxHeight = content.style.maxHeight ? null : content.scrollHeight + "px";
        });
      }
    }, false);
  }
}
05EvidenceCODE COMPARE
The code that does this

The background worker adds the UUID and posts the consent record

What it actually does
Readable equivalent of SaveConsentChoicescripts/background.build.min.js
class SaveConsentChoice {
  constructor() {
    this._addListener();
  }

  _addListener() {
    chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
      if ("SaveConsentChoice" === message.subject) {
        return this.saveRequest(message.value)
          .then(success => {
            sendResponse({ success: success });
          })
          .catch(error => {
            sendResponse({ success: false, error: error });
          }), true;
      }
    });
  }

  async saveRequest(consent) {
    var stored = await chrome.storage.sync.get(["uuid"]);
    await fetch("https://consentmangement.fdown.net/api/consent", {
      method: "POST",
      cache: "no-cache",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ uuid: stored.uuid, consent: consent })
    });
    return true;
  }
}
Readable equivalent of UUID reuse in video-platform detectionscripts/background.build.min.js
async fetchInfo(tab) {
  var url = tab.url;
  var tabId = tab.id;
  if (!this.videoPlatformDetectInfo[tabId] || this.videoPlatformDetectInfo[tabId].url !== url) {
    if (this.videoPlatformDetectInfo[tabId] = { score: null, url: url }, this.isValidUrl(url)) {
      try {
        var referrer = await this.getReferrer(tab);
        var response = await fetch(this.config.url, {
          method: "POST",
          cache: "no-cache",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ targetUrl: url, referrerUrl: referrer, userId: this.uuid })
        });
        var parsed = await response.json();
        this.videoPlatformDetectInfo[tabId] = { score: parsed.score, cause: parsed.cause, url: url };
        this.sendVideoPlatformDetectMessage(tabId);
      } catch (error) {
        this.videoPlatformDetectInfo[tabId] = void 0;
      }
    } else {
      this.sendVideoPlatformDetectMessage(tabId);
    }
  }
}

async getUuid() {
  const existing = await new Promise(resolve => {
    chrome.storage.sync.get(["uuid"], value => {
      resolve(value.uuid);
    });
  });
  if (existing && this.validateUUID4(existing)) return existing;
  const generated = this.makeUUID();
  return new Promise(resolve => {
    chrome.storage.sync.set({ uuid: generated }, async () => {
      resolve(generated);
    });
  });
}
06EvidenceTHIRD PARTY LIST
Remote services involved in this identifier flow
  • consentmangement.fdown.net

    Receives the consent choice together with the stored extension UUID.

  • videodetect.fdown.net

    Receives the same stored UUID as userId in video-platform detection requests.

What it can do

Permissions this extension asks for, as declared in version 6.5.5. 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

    http://*/* and 1 more

  • Read and change your data on every secure site you visit

    https://*/*

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

    tabs

  • Run its own code inside the pages you visit

    scripting

  • Watch every request your browser makes

    webRequest

  • Store an unlimited amount of data in your browser

    unlimitedStorage

  • See, disable and uninstall your other extensions, including your security ones

    management

  • Start, monitor and manage your downloads

    downloads

  • Store data in your browser

    storage

Updated 30 September 2026njgehaondchbmjmajphnhlojfnbfokng