66import sys
77import tempfile
88import zipfile
9- from typing import Any , List , Literal , Optional , Union
9+ from typing import Any , Dict , List , Literal , Optional , Union
1010
1111__all__ = [
1212 "Config" ,
@@ -51,40 +51,21 @@ def __init__(
5151 disable_webgl : Optional [bool ] = False ,
5252 proxy : Optional [str ] = None ,
5353 stealth : Optional [bool ] = False ,
54+ stealth_options : Optional [Dict [str , bool ]] = None ,
5455 timezone : Optional [str ] = None ,
5556 ** kwargs : Any ,
5657 ):
5758 """
5859 creates a config object.
59- Can be called without any arguments to generate a best-practice config, which is recommended.
60-
61- calling the object, eg : myconfig() , will return the list of arguments which
62- are provided to the browser.
63-
64- additional arguments can be added using the :py:obj:`~add_argument method`
65-
66- Instances of this class are usually not instantiated by end users.
67-
68- :param user_data_dir: the data directory to use (must be unique if using multiple browsers)
69- :param headless: set to True for headless mode
70- :param browser_executable_path: specify browser executable, instead of using autodetect
71- :param browser: which browser to use. Can be "chrome", "brave" or "auto". Default is "auto".
72- :param browser_args: forwarded to browser executable. eg : ["--some-chromeparam=somevalue", "some-other-param=someval"]
73- :param sandbox: disables sandbox
74- :param lang: language string to use other than the default "en-US,en;q=0.9"
75- :param user_agent: custom user-agent string
76- :param expert: when set to True, enabled "expert" mode.
77- This conveys, the inclusion of parameters: --disable-web-security ----disable-site-isolation-trials,
78- as well as some scripts and patching useful for debugging (for example, ensuring shadow-root is always in "open" mode)
79-
60+ ...
61+ :param stealth: enables stealth mode
62+ :param stealth_options: granular control over stealth patches
8063 :param kwargs:
8164 """
8265
8366 if not browser_args :
8467 browser_args = []
8568
86- # defer creating a temp user data dir until the browser requests it so
87- # config can be used/reused as a template for multiple browser instances
8869 self ._user_data_dir : str | None = None
8970 self ._custom_data_dir = False
9071 if user_data_dir :
@@ -98,10 +79,7 @@ def __init__(
9879 self .headless = headless
9980 self .sandbox = sandbox
10081
101- # BEST PRACTICE: Do NOT inject custom User-Agent.
102- # Let Chrome use its REAL, NATIVE User-Agent. Custom UAs create
103- # fingerprint mismatches (platform, version, etc) that anti-bots detect.
104- self .user_agent = user_agent # Only set if user explicitly provides one
82+ self .user_agent = user_agent
10583 self .host = host
10684 self .port = port
10785 self .expert = expert
@@ -111,21 +89,23 @@ def __init__(
11189
11290 self .proxy = proxy
11391 self .stealth = stealth
92+ self .stealth_options = stealth_options or {
93+ "patch_webdriver" : True ,
94+ "patch_canvas" : True ,
95+ "patch_audio" : True ,
96+ "patch_fonts" : True ,
97+ "patch_webgpu" : True ,
98+ "patch_client_hints" : True ,
99+ "patch_webgl" : True ,
100+ "patch_webrtc" : True ,
101+ "patch_battery" : True ,
102+ "patch_media_devices" : True ,
103+ "patch_permissions" : True ,
104+ "patch_chrome_runtime" : True ,
105+ }
114106 self .timezone = timezone
115-
116- if self .proxy :
117- # parse proxy string
118- # format: scheme://user:pass@host:port or host:port
119- if "://" not in self .proxy :
120- self .proxy = "http://" + self .proxy
121-
122- # We no longer create the extension.
123- # We rely on --proxy-server (added below) and CDP Fetch.authRequired (in browser.py)
124- # This mimics Playwright and avoids extension detection/issues.
125- logger .info (f"Configured proxy: { self .proxy } (Auth handled via CDP)" )
126107
127- # when using posix-ish operating system and running as root
128- # you must use no_sandbox = True, which in case is corrected here
108+ # ... (rest of the logic)
129109 if is_posix and is_root () and sandbox :
130110 logger .info ("detected root usage, auto disabling sandbox mode" )
131111 self .sandbox = False
@@ -136,21 +116,9 @@ def __init__(
136116 self .browser_connection_timeout = browser_connection_timeout
137117 self .browser_connection_max_tries = browser_connection_max_tries
138118
139- # other keyword args will be accessible by attribute
140119 self .__dict__ .update (kwargs )
141120 super ().__init__ ()
142- # STEALTH-LEVEL: Hardened command flags
143- #
144- # REMOVED (detectable as stealth driver):
145- # --enable-automation (exposes navigator.webdriver)
146- # --disable-component-update (flags as automation)
147- # --disable-popup-blocking (flags as automation)
148- # --disable-default-apps (flags as automation)
149- # --disable-extensions (blocks our proxy auth extension)
150- #
151- # ADDED:
152- # --disable-blink-features=AutomationControlled (hides webdriver)
153- #
121+
154122 self ._default_browser_args = [
155123 "--remote-allow-origins=*" ,
156124 "--no-first-run" ,
@@ -169,6 +137,11 @@ def __init__(
169137 "--disable-blink-features=AutomationControlled" ,
170138 "--disable-session-crashed-bubble" ,
171139 "--disable-search-engine-choice-screen" ,
140+ # GPU/WebGL Hardening
141+ "--use-gl=angle" ,
142+ "--enable-webgl" ,
143+ "--ignore-gpu-blocklist" ,
144+ "--enable-accelerated-2d-canvas" ,
172145 ]
173146
174147 @property
0 commit comments