Is FDev Security safe?

Medium 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
45Risk

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 FOUND

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

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.

There is no setting to turn off this per-navigation check-in.

01EvidenceCAUSE EFFECT
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.

02EvidenceFIELD TABLE
What the domain-check request sends and gets back
FieldValueWhy it matters
Visited hostname
en.wikipedia.orgThe 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.
03EvidenceCODE COMPARE
The code that does this

The navigation listener and outbound POST, as shipped and deobfuscated

What it actually does
(() => {
    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
          })
        }))
      }
    }))
  })()
})();
04EvidenceTHIRD PARTY LIST
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.

05EvidenceARTIFACT
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.

RequiresNode.js 18+npm i puppeteerUnpacked 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. 1
    Unzip the extension's CRX to ./fdev-security-unpacked.
  2. 2
    npm i puppeteer.
  3. 3
    node verify_domain_check.js ./fdev-security-unpacked
06EvidencePLAIN NOTE
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.

Data recipients

lenhatthanh.com
Updated 20 September 2026aehklbpkgdnlphgdbjgmlinnlmonpbcn