Research · · 8 min read

Detecting Hidemium: Fingerprinting inconsistencies in anti-detect browsers

Detecting Hidemium: Fingerprinting inconsistencies in anti-detect browsers

This is the fourth article in our series on anti-detect browsers. In the previous post, we explained how to detect anti-fingerprinting scripts injected via Chrome DevTools Protocol (CDP). Here, we analyze Hidemium, a popular anti-detect browser, and describe how it can be detected.

We start with a high-level overview of Hidemium’s features and how it modifies browser fingerprints. Next, we reverse engineer its JavaScript injection behavior. Finally, we propose a JS-based detection challenge tailored to Hidemium.

Scope note: This article focuses on in-browser JavaScript-based detection. Hidemium also performs fingerprint obfuscation at the browser engine level, e.g., canvas randomization, which is not covered here.

Overview of Hidemium

Hidemium is a free anti-detect browser available on macOS and Windows.

While the base version is free, paid plans unlock additional capabilities, such as a wider range of operating systems and browsers, and access to browser fingerprints based on real-user data.

Like many anti-detect browsers, Hidemium uses the concept of profiles to isolate and customize browser instances. Each profile acts as a unique configuration that defines the device fingerprint and network setup. Parameters include:

Each browser profile runs in a fully isolated environment, with separate configuration files, cookies, and local storage. This isolation prevents session sharing and helps maintain the illusion of distinct users operating from different devices and geolocations.

Analyzing Hidemium

To understand how Hidemium modifies the browser fingerprint, we start by inspecting a page load in Chrome DevTools. When visiting example.com, we observe that Hidemium injects a fingerprint manipulation script via a locally loaded Chrome extension named Hidemium.

The extension isn’t sourced from the Chrome Web Store.

Instead, it’s loaded directly from Hidemium’s install directory. Its manifest.json includes the following configuration:

"content_scripts": [{
        "matches": ["https://*/*"],
        "js": [
			"./lib/content.js"
		],
        "run_at": "document_end"
    }],

This means the script is injected after the DOM has been constructed, but before subresources like images or frames have loaded, a window that allows execution before most client-side detection logic runs.

Once injected, the script defines an inject function that bundles various anti-fingerprinting techniques. It inserts this function into the main JavaScript context and then immediately removes the script tag to avoid leaving visible traces.

const inject = async () => {
	// JS code related to anti fingerprinting
}

// Inject script in the main JS execution context and delete it after execution
const script_1 = document.createElement("script");
		script_1.textContent = "(" + inject + ")()";
document.documentElement.appendChild(script_1);
script_1.remove();

Setting a breakpoint at script_1.remove() shows the script executes early in the page lifecycle, before any detection scripts can hook into or observe the DOM.

The injected script begins by identifying the operating system selected in the Hidemium profile (not the genuine host OS). It does so using the user agent string:

const isWindows = () => {
  return navigator.userAgent && navigator.userAgent.includes("Windows");
};

const isMacOS = () => {
  return navigator.userAgent && navigator.userAgent.includes("Macintosh");
};

const isUbuntu = () => {
  return navigator.userAgent && navigator.userAgent.includes("X11; Linux");
};

const isChromeOS = () => {
  return navigator.userAgent && navigator.userAgent.includes("X11; CrOS");
};

Based on this value, Hidemium deletes or alters specific browser properties to align the browser fingerprint with the spoofed OS. Below are a few examples:

const windows = () => {
  delete window.NetworkInformation.prototype.downlinkMax;
  delete window.BarcodeDetector;
};

const ubuntu = () => {
  delete Object.getPrototypeOf(window.navigator).canShare;
  delete Object.getPrototypeOf(window.navigator).share;
  delete window.NetworkInformation.prototype.downlinkMax;

delete Object.getPrototypeOf(window.navigator).clearAppBadge;
  delete Object.getPrototypeOf(window.navigator).setAppBadge;
  const delWindowFeatures = ["BarcodeDetector","Serial"];
  delWindowFeatures.map((e) => window[e] && delete window[e]);
};

const macOS = () => {
  if(navigator.userAgent.includes("Version/")){
    fakeSafari();
  }
  delete Object.getPrototypeOf(window.navigator).canShare;
  delete Object.getPrototypeOf(window.navigator).share;
  delete window.NetworkInformation.prototype.downlinkMax;
  const delWindowFeatures = [
    "BarcodeDetector",
    "ReportBody",
    "Report",
    "InterventionReportBody",
    "DocumentPolicyViolationReportBody",
    "DeprecationReportBody",
    "CoopAccessViolationReportBody",
    "CSPViolationReportBody",
    "PaymentInstruments",
  ];
  delWindowFeatures.map((e) => window[e] && delete window[e]);
};

The relevant function is then executed based on the spoofed OS:

if (isWindows()) windows();

if (isMacOS()) macOS();

if (isUbuntu()) ubuntu();

MacOS-specific fingerprint spoofing

The macOS function is responsible for applying lies in case the user selected a MacOS device in their Hidemium profile.

If the selected Hidemium profile pretends to be Safari, then it executes the fakeSafari function. This function takes care of deleting APIs linked to the Chromium browser (since Hidemium is based on Chromium) to make it look more like a real Safari browser. It also defines variables like window.safari that should exist in a Safari browser. Note that Hidemiun lies in a subtle way so that the window.safari object looks like a native object. For example, it overrides the associated toString method to return "[object Safari]" instead of the non-native code used to define the object.

window.safari = {
  pushNotification: new SafariRemoteNotification(),
  __defineGetter__: function(propertyName, getterFunction) {
    Object.defineProperty(this, propertyName, {
      get: getterFunction
    });
  },
  __defineSetter__: function(propertyName, setterFunction) {
    Object.defineProperty(this, propertyName, {
      set: setterFunction
    });
  },
  __lookupGetter__: function(propertyName) {
    return Object.getOwnPropertyDescriptor(this, propertyName)?.get;
  },
  __lookupSetter__: function(propertyName) {
    return Object.getOwnPropertyDescriptor(this, propertyName)?.set;
  },
  constructor: function() {
    console.log("Safari object constructor");
  },
  hasOwnProperty: function(propertyName) {
    return this.hasOwnProperty(propertyName);
  },
  isPrototypeOf: function(property) {
    return Object.prototype.isPrototypeOf.call(this, property);
  },
  propertyIsEnumerable: function(propertyName) {
    return this.propertyIsEnumerable(propertyName);
  },
  toLocaleString: function() {
    return this.toString();
  },
  toString: function() {
    return "[object Safari]";
  },
  valueOf: function() {
    return this;
  }
};

Then it deletes a few properties linked to the navigator object like navigator.canShare and it deletes a few window object properties like "BarcodeDetector", "ReportBody", "Report".

How can we detect the Hidemium anti-detect browser?

To test Hidemium in a realistic and more difficult scenario, we focus on detecting it without relying on browser or OS spoofing artifacts. These artifacts, e.g., inconsistently overridden native API,s are often low-hanging fruit for detection. But in this case, we simulate a user who plays by Hidemium’s rules.

In fact, Hidemium explicitly warns users against misrepresenting their true OS, likely to minimize the risk of detection from poorly mimicked environments.

To reflect this, we create a Hidemium profile that matches our actual environment: Chrome on macOS. Because there's no OS spoofing involved, the fakeSafari function is not triggered. However, the macOS logic still deletes several properties from the navigator and window objects to better align with the chosen fingerprint.

The following three properties are removed from the runtime environment:

delete Object.getPrototypeOf(window.navigator).canShare;
delete Object.getPrototypeOf(window.navigator).share;
delete window.NetworkInformation.prototype.downlinkMax;

This introduces two minor inconsistencies. On a genuine Chrome 134 browser running on macOS, navigator.share and navigator.canShare are both defined:

In the Hidemium environment, these same properties are missing:

Similarly, the macOS function deletes several window object properties, which introduces inconsistencies.

const delWindowFeatures = [
  "BarcodeDetector",
  "ReportBody",
  "Report",
  "InterventionReportBody",
  "DocumentPolicyViolationReportBody",
  "DeprecationReportBody",
  "CoopAccessViolationReportBody",
  "CSPViolationReportBody",
  "PaymentInstruments",
];
delWindowFeatures.map((e) => window[e] && delete window[e]);

As a result, these features are absent in Hidemium:

In contrast, a legitimate version of Chrome exposes most of these APIs:

This behavior creates a subtle but valuable fingerprinting opportunity. For example, the BarcodeDetector API is expected to be available starting in Chrome 88 on macOS, as confirmed by the MDN documentation.

By comparing the presence or absence of APIs that should be defined for a specific browser-version-platform combination, we can build robust JavaScript challenges to identify inconsistencies and detect the use of Hidemium, even when no blatant lies are in play.

if (os ==='macOS' && browser === 'Chrome' && browserVersion > 88) {
	if (!('BarcodeDetector' in window) && !('ReportBody' in window) && !('CSPViolationReportBody' in window) && !('share' in navigator) && !('canShare' in navigator)) {
		console.log('Hidemium detected!')
	}
}

The detection test above is quite strict. It verifies that none of the APIs are present. We could create a more permissive test that verifies whether any (one or more) of the expected APIs are not present.

Discussion on detection limits and next steps

The detection method we explained works well for one specific case: when Hidemium runs as Chrome on macOS without trying to fake the browser or OS. In that case, it deletes a few browser APIs that should normally exist, and we can use that to detect it. But this method is also fragile. If Hidemium changes its behavior and stops deleting these properties, the detection will stop working. Also, Hidemium can be used with many different browsers like Yandex or Opera. These profiles may behave differently, so the same detection logic might not apply.

This shows the bigger challenge: anti-detect browsers keep changing how they hide themselves. To keep up, defenders need to build detection techniques that work across more cases, not just one specific setup.

Instead of checking for a fixed list of deleted APIs, it’s better to check whether important browser features match what we expect for a given browser, version, and operating system. You can find this kind of information in browser release notes or compatibility sites like caniuse.com. For example, if a browser says it’s Chrome 134 on macOS, but BarcodeDetector is missing, that’s a sign that something is off.

Moreover, anti-detect browsers often change values deep in the browser engine, for example by adding random noise to canvas or audio fingerprints. These changes don’t show up in simple API checks.

To catch that kind of behavior, you need to run JavaScript challenges that test how the browser behaves when rendering canvas, audio, or WebGL content. These tests help reveal if something has been tampered with.

In short, to detect anti-detect browsers like Hidemium in a reliable way, we recommend combining two types of checks:

This approach gives you a stronger and more flexible way to find bots, even those using tools or profiles you haven’t seen before.

Conclusion

In this article, we examined how Hidemium, a widely used anti-detect browser, modifies browser APIs to hide automation and fake device fingerprints. We showed how, even without browser or OS spoofing, Hidemium introduces subtle inconsistencies that can be used for detection, such as missing APIs that should exist in genuine Chrome on macOS.

While our detection example targets a specific setup, it illustrates a broader principle: strong detection should not rely on fixed signatures. Instead, it should combine expected feature checks with dynamic fingerprint challenges to uncover signs of tampering.

As anti-detect tools evolve, detection strategies must do the same. Building flexible, behavior-based tests is key to identifying both known and emerging threats and staying ahead of automation that tries to blend in.

Read next