Is FDev Security safe?

Low risk

FDev Security sends the hostname of every site you visit to its developer's own server, with no way to turn it off.

On every top-level page load, the extension checks the site's hostname against a locally cached allow list and, if it isn't there, sends that hostname to lenhatthanh.com/api/v1/domain-check.php. The server's response decides whether a warning screen is injected into the page. This check runs unconditionally at startup and, unlike the extension's other blocking features, has no corresponding setting in the options page to disable it.

lenhatthanh20v2.0.2Chrome Web Store
19Risk
Who publishes it

lenhatthanh20 - 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
lenhatthanh20

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

Publishers can request a review.

Findings

FDev Security reports every website you visit to the developer's own server

HighunwantedCWE-359Static analysis

Code analysis shows the background service worker sends the hostname of every page you visit to the developer's own domain, lenhatthanh.com, for every new site.

What actually happens

You did this

You open any top-level webpage, from a search result to your work email.

The extension did this

The background service worker sends that page's hostname to the developer's own server as the page begins loading.

This runs for ordinary, unrelated sites like example.com or wikipedia.org, not just Facebook or Messenger.

What the domain-check request sends and gets back

  • Visited hostname
    en.wikipedia.org

    The domain of the page you just opened, sent for every new top-level navigation.

  • Safety verdict
    {"isSafety": true}

    The server's flag telling the extension whether to show you an unsafe-site warning.

The code that does this

The navigation listener and outbound POST, as shipped and deobfuscated

Readable version
(() => {    let e = !0;    var t;    t = async t => {      var o;      e = !0;      const c = null !== (o = await (async e => {          const t = await chrome.storage.sync.get(e);          return t[e] ? t[e] : null        })("allowList")) && void 0 !== o ? o : [],        r = new URL(t.url).hostname;      if (!c.some((e => e.includes(r)))) try {        await (async t => {          var o;          const c = await fetch("https://lenhatthanh.com/api/v1/domain-check.php", {              method: "POST",              headers: {                "Content-Type": "application/json"              },              body: JSON.stringify({                domain: t              })            }),            r = await c.json();          e = null === (o = r.isSafety) || void 0 === o || o        })(r)      } catch (e) {        console.warn(e)      }    }, chrome.webRequest.onSendHeaders.addListener(t, {      urls: ["*://*/*"],      types: ["main_frame"]    }, ["requestHeaders"]), chrome.tabs.onUpdated.addListener(((t, o, c) => {      var r;      if ("complete" === o.status && !e && c) {        const e = null !== (r = c.url) && void 0 !== r ? r : "";        if (!e.startsWith("http://") && !e.startsWith("https://")) return;        const o = "./scripts/realtime-protection.content-script.js";        chrome.scripting.executeScript({          target: {            tabId: t          },          files: [o]        }, (() => {          if (chrome.runtime.lastError) {            const e = `Script injection failed: ${chrome.runtime.lastError.message}`;            console.warn(e)          } else((e, t) => {            chrome.tabs.sendMessage(e, t)          })(t, {            type: "dangerousSiteDetected",            url: e          })        }))      }    }))  })()})();

Where the hostname goes

    • lenhatthanh.com

    Receives the hostname of every top-level page you visit and returns a safety verdict used to show or suppress the extension's own warning modal.

Reproduce it yourself

Loads the unpacked extension in Chromium and confirms a domain-check POST fires for ordinary, unrelated sites, not just Facebook or Messenger.

Requires
  • Node.js 18+
  • npm i puppeteer
  • Unpacked copy of the extension
verify_domain_check.js · js
// verify_domain_check.js// Loads the extension unpacked in real Chromium and confirms a POST to// the domain-check endpoint fires for ordinary, unrelated sites, not just// Facebook or Messenger.const puppeteer = require('puppeteer');const path = require('path');(async () => {  const extensionPath = path.resolve(process.argv[2] || './fdev-security-unpacked');  const browser = await puppeteer.launch({    headless: false, // MV3 service workers need a headed context to load    args: [      `--disable-extensions-except=${extensionPath}`,      `--load-extension=${extensionPath}`,    ],  });  const targets = ['https://example.com/', 'https://en.wikipedia.org/wiki/Special:Random'];  const seen = [];  for (const url of targets) {    const page = await browser.newPage();    page.on('request', (req) => {      if (req.url().startsWith('https://lenhatthanh.com/api/v1/domain-check.php')) {        seen.push({ visited: url, body: req.postData() });      }    });    await page.goto(url, { waitUntil: 'networkidle0' });    await new Promise((r) => setTimeout(r, 2000));    await page.close();  }  console.log(JSON.stringify(seen, null, 2));  console.log(    seen.length === targets.length      ? 'CONFIRMED: a domain-check POST fired for every visited site.'      : 'NOT CONFIRMED: fewer domain-check POSTs than visited sites.'  );  await browser.close();})();
How to run it
  1. 1Unzip the extension's CRX to ./fdev-security-unpacked.
  2. 2npm i puppeteer.
  3. 3node verify_domain_check.js ./fdev-security-unpacked

Observation

Static analysis finding. This behaviour was identified by reading the shipped extension code and has not yet been reproduced in a live run. The trigger conditions and the exact data sent are read from the code, not from an observed capture.

What it can do

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

    <all_urls>

  • Keep running in the background while your browser is open

    background

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

    activeTab

  • Block and redirect the requests your browser makes

    declarativeNetRequest

  • Store data in your browser

    storage

  • 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

declarativeNetRequestWithHostAccess

Where it sends data

Destinations our analysis observed FDev Security contacting. Sending data somewhere is not a finding on its own - an extension that syncs your settings has to talk to its own server - but it is where your data can go, and who else it goes to.

  • lenhatthanh.com

    FDev Security sends data to lenhatthanh.com. No other extension we have analyzed sends data here.

Updated 2 November 2026aehklbpkgdnlphgdbjgmlinnlmonpbcn