Custom browser flag configurations to maximise stealth
Custom browser flag configurations to maximise stealth
Most operators treat antidetect software as a black box. you buy a Multilogin or AdsPower seat, spin up a profile, assign a proxy, and hope the fingerprint holds. that works until it doesn’t. when you start running a hundred-plus profiles across e-commerce platforms, ad accounts, or affiliate funnels, the black-box approach starts costing real money, because you don’t know which layer failed when an account gets flagged.
the layer that fails most often, in my experience, isn’t the proxy. it’s the browser itself. specifically, it’s the gap between what the browser reports about itself and what the underlying Chromium binary actually does. that gap shows up in flag-level telemetry that most operators never inspect. platforms that run serious fraud detection, think Meta’s signal stack, Google’s TrustToken infrastructure, or PerimeterX on the e-commerce side, have moved well beyond simple user-agent checks. they’re correlating flag-derived behaviors: automation API exposure, renderer quirks, hardware concurrency mismatches, and timing artifacts.
this piece is about getting into that layer. it assumes you’ve used antidetect browsers before and understand what canvas fingerprinting is. it does not assume you’re a Chromium engineer. what i want to give you is a working mental model of which flags matter, why they matter, and what breaks when you get them wrong.
background and prior art
Chromium’s command-line switch architecture has been around since the early Chrome builds. the canonical community-maintained list of switches runs to thousands of entries, most of which are irrelevant to fingerprinting. the ones that matter for stealth are concentrated in three areas: automation API exposure, renderer configuration, and hardware abstraction.
the automation-detection problem got serious attention in 2018-2019 when Headless Chrome became the go-to tool for scraping and bot operations. at that point, navigator.webdriver was set to true by default in any automated session, and CDP-based tools like Puppeteer left obvious traces in the window.chrome object and the permissions API response set. the antidetect browser space responded by patching those values at the JS runtime layer. platforms responded by looking deeper, at flag-level consistency signals. the arms race that followed is where we are now. tools like Playwright added –disable-blink-features=AutomationControlled as a semi-official workaround, and antidetect vendors began building proprietary flag sets on top of patched Chromium forks.
what’s changed in 2025-2026 is that ML-based fingerprint classifiers have gotten cheap enough to run at request time. a classifier trained on millions of real browser sessions can now flag a configuration where the reported GPU renderer doesn’t match what the Canvas API actually produces in under 50ms. that means flag-level consistency, not just JS-level spoofing, is now load-bearing.
the core mechanism
a Chromium binary accepts flags at launch time. these flags configure everything from network stack behavior to GPU acceleration mode. some flags are stable and documented. many are internal and change between releases. the ones relevant to stealth cluster around a few subsystems.
automation API surface
the most important single flag for any automated or semi-automated workflow is:
--disable-blink-features=AutomationControlled
this removes the navigator.webdriver IDL attribute from the blink rendering engine. without it, any page can read navigator.webdriver === true and know it’s dealing with an automated session. this is table stakes and every antidetect browser handles it, but it’s worth knowing explicitly because if you’re building a custom Puppeteer or Playwright setup, you have to set it yourself.
related flags that reduce automation API exposure:
--disable-features=AutomationControlled
--disable-extensions-except=/path/to/extension
--no-first-run
--no-service-autorun
--password-store=basic
the --no-first-run and --no-service-autorun flags matter because without them, Chrome emits specific IPC signals on startup that differ from a user-initiated launch. some platforms sample those timing patterns.
renderer and GPU configuration
this is where most operators leave money on the table. the GPU renderer string is surfaced through WebGL:
const gl = canvas.getContext('webgl');
const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);
a real M3 MacBook Pro returns something like ANGLE (Apple, ANGLE Metal Renderer: Apple M3 Pro, Unspecified Version). a headless Chrome on a bare-metal Linux box with software rendering returns ANGLE (Google, Vulkan 1.3.0 (SwiftShader Device (Subzero) (0x0000C0DE)), SwiftShader driver-5.0.0). SwiftShader is a massive red flag for detection systems, because legitimate consumer browsers almost never run it.
the fix is to use the host machine’s actual GPU and pass:
--use-angle=gl
--use-gl=desktop
if you’re on a headless server without a physical GPU, you need a virtual framebuffer or a GPU-passthrough setup. Xvfb with a real GPU driver is the practical path on Linux. the alternative is to match your software renderer string to a real device profile, which is what Multilogin and AdsPower do internally with their GPU masking layers, but doing it at the flag level is more reliable because it changes what the renderer actually does, not just what it reports.
hardware concurrency and memory
navigator.hardwareConcurrency returns the number of logical CPU cores. navigator.deviceMemory returns a bucket value (0.25, 0.5, 1, 2, 4, 8). these are read-only in JS but they’re derived from the actual system configuration. you can override them in JS:
Object.defineProperty(navigator, 'hardwareConcurrency', { get: () => 8 });
Object.defineProperty(navigator, 'deviceMemory', { get: () => 8 });
but JS-level overrides are detectable if the platform runs timing tests. if you claim 8 cores and then run a multithreaded benchmark via SharedArrayBuffer or Web Workers, the timing doesn’t match. the correct approach is to match the claimed value to the actual system configuration, or to use a system with the hardware you’re claiming.
language and locale flags
--lang=en-US
--accept-lang=en-US,en;q=0.9
these set navigator.language, navigator.languages, and the Accept-Language HTTP header. the critical thing is consistency: all three values need to match each other and match the proxy’s geolocation. a proxy in Frankfurt with --lang=en-US is fine. a proxy in Frankfurt with --lang=de-DE and a user-agent claiming an English Windows install is a consistency failure.
window and screen geometry
--window-size=1920,1080
--window-position=0,0
window.screen.width, window.screen.height, window.innerWidth, and window.devicePixelRatio all need to be consistent with each other and with the user-agent’s implied device class. a mobile user-agent on a 1920x1080 window is an instant flag. device pixel ratio matters too: a claimed Retina display should have devicePixelRatio of 2.0, and the canvas output should reflect that.
network stack flags
--host-resolver-rules="MAP * ~NOTFOUND , EXCLUDE myproxy.com"
--proxy-server=socks5://user:pass@host:port
--proxy-bypass-list="<-loopback>"
these are relevant if you’re routing at the flag level rather than through a system proxy. the important thing is that DNS resolution needs to go through the proxy too, not just TCP. a SOCKS5 proxy that routes TCP but leaks DNS to the host resolver is a classic WebRTC/DNS leak pattern that platforms actively test for.
worked examples
example 1: Playwright stealth profile for a Meta ad account
running a fresh Playwright script in 2025 without flag customization gets flagged within the first session on Meta’s login page. here’s the flag set that’s worked reliably for me on Ubuntu 22.04 with an NVIDIA GPU:
from playwright.async_api import async_playwright
launch_args = [
'--disable-blink-features=AutomationControlled',
'--disable-features=IsolateOrigins,site-per-process',
'--lang=en-US',
'--accept-lang=en-US,en;q=0.9',
'--window-size=1440,900',
'--use-angle=gl',
'--use-gl=desktop',
'--no-first-run',
'--no-service-autorun',
'--password-store=basic',
'--disable-dev-shm-usage',
]
async with async_playwright() as p:
browser = await p.chromium.launch(
args=launch_args,
headless=False,
)
the --disable-dev-shm-usage flag is Linux-specific and prevents Chrome from crashing in low-memory environments, but it also changes shared memory behavior in ways that headless detection can pick up. running with headless=False through a virtual display costs more compute but produces a behavioral fingerprint that’s much closer to a real session.
cost to run this at scale: on a dedicated server with 32 cores and an RTX 3060 (about $120/month on Hetzner’s GPU line), you can run 40-60 simultaneous non-headless sessions with Xvfb. at $120/month that’s roughly $2-3 per profile-month for compute before proxy costs.
example 2: Dolphin Anty with custom flag injection
Dolphin Anty at its Pro tier ($89/month for 100 profiles as of Q1 2026) exposes a customFlags array in its profile API. most users don’t touch it. the default profile already handles --disable-blink-features=AutomationControlled and basic navigator spoofing, but it does not handle GPU renderer consistency for all host configurations.
in their REST API, when creating or updating a profile:
{
"name": "profile-001",
"platform": "windows",
"browserType": "anty",
"mainWebsite": "facebook",
"proxy": {
"type": "socks5",
"host": "proxy.example.com",
"port": 1080,
"login": "user",
"password": "pass"
},
"browserFlags": [
"--use-angle=gl",
"--use-gl=desktop",
"--disable-features=AudioServiceOutOfProcess"
]
}
the --disable-features=AudioServiceOutOfProcess flag matters for audio context fingerprinting. the audio context fingerprint is generated by running a short oscillator through a dynamics compressor and reading the output buffer. when the audio service runs out-of-process (the default on most platforms), the output can differ from what’s expected for the claimed OS. collapsing it into the main process makes the output more consistent with Windows behavior.
i’ve run this configuration on 200+ profiles for affiliate lead generation campaigns in the finance vertical. account longevity improved noticeably once the GPU and audio flags were added, though i want to be clear that there are too many variables in any production environment to attribute that to a single change.
example 3: AdsPower fingerprint audit workflow
AdsPower (paid plans from $9/month) has a built-in fingerprint check that opens a browser profile and visits a detection page. the check they use internally is similar to what BrowserLeaks and CreepJS report, but it doesn’t show you the raw flags driving those results.
the practical workflow i use: open a profile in AdsPower, then visit coveryourtracks.eff.org (run by the EFF, a real primary source for fingerprint uniqueness testing). note the canvas hash, the WebGL hash, and the audio fingerprint. then open the same profile with a custom flag injected via AdsPower’s “Custom flags” field in the advanced browser settings, and re-run the test. the hashes should change only in ways that are consistent with the claimed device profile.
if the canvas hash changes but the WebGL hash doesn’t when you add noise, that’s a sign the WebGL path is hardcoded in the antidetect fork rather than dynamically generated, which is a consistency risk.
edge cases and failure modes
flag-JS inconsistency
the most common failure mode is setting a flag that changes runtime behavior without updating the corresponding JS-reported value. example: using --force-device-scale-factor=2 to get a 2x device pixel ratio, but not patching window.devicePixelRatio in the profile’s JS injection. the actual canvas output is now 2x resolution but devicePixelRatio reports 1. that inconsistency is trivially detectable.
mitigation: whenever you add a flag that affects a hardware characteristic, check whether there’s a corresponding JS property and verify both. MDN’s Navigator API documentation is the reference for what’s accessible.
–no-sandbox as a detection signal
--no-sandbox is commonly added to Docker-based setups because Chrome won’t launch in most containers without it. it’s also a known automation signal. platforms running behavioral fingerprinting have trained on the difference between sandboxed and sandboxless sessions, particularly in how window.open() behaves with the noopener attribute and how permission prompts fire.
the correct fix is to run Chrome in a user namespace that supports sandboxing, not to disable the sandbox. on Linux this means ensuring seccomp and user namespaces are available in the container runtime. if your platform won’t support that, use --security-opt seccomp=unconfined in Docker rather than --no-sandbox in Chrome. the behavioral difference is smaller.
stale flag sets across Chromium versions
Chromium removes and renames flags between major versions. a flag that works on Chromium 120 (the base for most 2024 antidetect browsers) may not exist on Chromium 130 or may do something different. AdsPower and Dolphin Anty are both currently shipping Chromium 128-130 range forks as of early 2026. if you maintain a custom flag list and your vendor updates their Chromium base, you need to audit the list.
the way to check: launch the browser with --version and note the base Chromium version, then check the Chromium project’s feature flags changelog against your flag list. flags prefixed with --disable-features= are particularly volatile because the feature list changes every major version.
timing attacks on hardware claims
if you claim a high-performance device (navigator.hardwareConcurrency: 16, deviceMemory: 8) but your server is actually a 2-core VPS doing something CPU-intensive, platform-side timing probes will catch the mismatch. the audio context fingerprint and certain CSS animation tests have microsecond-level timing variance that correlates with actual CPU load.
mitigation: size your actual compute to match your claimed compute profile. if you’re claiming 8-core machines, run on 8-core machines. this sounds obvious but a lot of operators run 4-core VPSes with profiles claiming 16-core MacBook Pros.
WebRTC policy flags
--enforce-webrtc-ip-usage-policy
--webrtc-ip-handling-policy=disable_non_proxied_udp
without these, WebRTC can expose the host machine’s real IP even through a proxy. these flags are more reliable than JS-level WebRTC blocking because they operate at the network stack level. however, they also change how WebRTC behaves for legitimate calls, which platforms can detect if they’re specifically probing WebRTC functionality. for most multi-account use cases where you’re not doing video calls, setting these is the right call. for platforms where WebRTC is a core product feature, you need to think more carefully.
what we learned in production
the most important thing i’ve learned running flag-customized profiles at scale is that the marginal value of each additional flag drops off fast, and the consistency risk of each additional flag is roughly constant. every flag you add is another dimension on which a consistency failure can occur. the operators i’ve seen get into trouble are usually the ones who’ve accumulated 30-40 custom flags over time without auditing whether they’re still needed or whether they’re interacting with each other.
my current baseline for a generic Chromium-based profile is nine flags: the automation control flag, two renderer flags, two language flags, the window size flag, two WebRTC flags, and the audio process flag. everything beyond that needs a specific justification tied to a platform behavior i’ve observed. i audit the list every time the underlying Chromium version changes, which for most commercial antidetect tools is every 3-4 months.
the other production lesson is about the difference between what detection systems care about today versus what they’ll care about in six months. the GPU renderer consistency problem became serious in 2024. the audio context timing problem started getting attention in late 2024 and is now actively exploited by some platforms. the next frontier, based on what i’m seeing in research from the browser security community and from discussions in the multi-account operator space at multiaccountops.com/blog/, is likely font rendering metrics and CSS computed style timing. those aren’t controllable through flags today, which means the flag layer is necessary but not sufficient. the flag layer buys you time and eliminates the easy signals. the hard signals require patches to the Chromium rendering engine itself, which is why the serious antidetect vendors are maintaining full Chromium forks rather than just wrapping stock Chrome.
one more note: there’s no flag combination that makes a browser completely undetectable. the goal is to make your profiles indistinguishable from the real-user population at the platform’s current detection threshold, which is a moving target. what works today on a given platform may not work in 90 days. treat flag configurations as a maintenance artifact, not a set-and-forget solution. for related context on how platforms think about multi-session detection, my review of canvas fingerprinting countermeasures and the proxy selection guide for multi-account setups go into the adjacent layers in more detail. the full index is at /blog/.
references and further reading
-
Chromium command-line switches reference – the official Chromium project page on launching Chrome with flags, maintained by Google’s Chromium team.
-
MDN Web Docs: Navigator API – Mozilla’s reference for all navigator properties accessible from JavaScript, including hardwareConcurrency, deviceMemory, and languages.
-
EFF Cover Your Tracks – the Electronic Frontier Foundation’s browser fingerprinting test, useful for auditing what a configured profile actually exposes.
-
Chrome DevTools Protocol documentation – Google’s official CDP reference, which documents how automation-layer APIs interact with the rendering engine and what flags affect them.
-
W3C Permissions Policy specification – the formal spec governing how browser features are exposed to pages, relevant when using
--disable-featuresflags that map to permission-controlled APIs.
Written by Xavier Fok
disclosure: this article may contain affiliate links. if you buy through them we may earn a commission at no extra cost to you. verdicts are independent of payouts. last reviewed by Xavier Fok on 2026-05-19.