From 351e99ed18f1a2d73f5c3a48923ef59f07c69e15 Mon Sep 17 00:00:00 2001 From: daijro Date: Sun, 6 Oct 2024 21:53:54 -0500 Subject: [PATCH] pythonlib: Add more leak warnings 0.2.8 Warns the user for when the passed parameters are likely to cause a leak. --- .gitignore | 30 ++++++++---- pythonlib/camoufox/exceptions.py | 8 ---- pythonlib/camoufox/fingerprints.py | 7 ++- pythonlib/camoufox/pkgman.py | 9 ++++ pythonlib/camoufox/utils.py | 75 ++++++++++++++++++++++++------ pythonlib/camoufox/warnings.py | 44 ++++++++++++++++++ pythonlib/camoufox/warnings.yml | 32 +++++++++++++ pythonlib/pyproject.toml | 2 +- 8 files changed, 171 insertions(+), 36 deletions(-) create mode 100644 pythonlib/camoufox/warnings.py create mode 100644 pythonlib/camoufox/warnings.yml diff --git a/.gitignore b/.gitignore index 4dfa7fe..09a07c9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,20 +1,32 @@ +# Local builds /camoufox-* /firefox-* /mozilla-unified -/extra-docs -/.vscode -_old/ dist/ bin/ -venv/ -/bundle/fonts/extra launch launch.exe + +# Internal testing +/extra-docs +/tests +/.vscode +/bundle/fonts/extra +pythonlib/*.png +scripts/*.png +test* + +# Old data +_old/ *.old -__pycache__/ -*.pyc + +# Logs wget-log *.kate-swp *.log -test.py -*.mmdb + +# Python interface +venv/ +__pycache__/ +*.pyc +*.mmdb \ No newline at end of file diff --git a/pythonlib/camoufox/exceptions.py b/pythonlib/camoufox/exceptions.py index a34e207..bcbb6d5 100644 --- a/pythonlib/camoufox/exceptions.py +++ b/pythonlib/camoufox/exceptions.py @@ -116,11 +116,3 @@ class InvalidOS(ValueError): """ ... - - -class DetectionWarning(RuntimeWarning): - """ - Raised when a the user has a setting enabled that can cause detection. - """ - - ... diff --git a/pythonlib/camoufox/fingerprints.py b/pythonlib/camoufox/fingerprints.py index a5ffd16..19e79a5 100644 --- a/pythonlib/camoufox/fingerprints.py +++ b/pythonlib/camoufox/fingerprints.py @@ -1,14 +1,13 @@ -import os.path import re from dataclasses import asdict from typing import Any, Dict, Optional from browserforge.fingerprints import Fingerprint, FingerprintGenerator -from yaml import CLoader, load + +from camoufox.pkgman import load_yaml # Load the browserforge.yaml file -with open(os.path.join(os.path.dirname(__file__), 'browserforge.yml'), 'r') as f: - BROWSERFORGE_DATA = load(f, Loader=CLoader) +BROWSERFORGE_DATA = load_yaml('browserforge.yml') FP_GENERATOR = FingerprintGenerator(browser='firefox', os=('linux', 'macos', 'windows')) diff --git a/pythonlib/camoufox/pkgman.py b/pythonlib/camoufox/pkgman.py index dbdef4b..d7579d7 100644 --- a/pythonlib/camoufox/pkgman.py +++ b/pythonlib/camoufox/pkgman.py @@ -16,6 +16,7 @@ import requests from platformdirs import user_cache_dir from tqdm import tqdm from typing_extensions import TypeAlias +from yaml import CLoader, load from .exceptions import UnsupportedArchitecture, UnsupportedOS @@ -337,3 +338,11 @@ def unzip( with ZipFile(zip_file) as zf: for member in tqdm(zf.infolist(), desc=desc): zf.extract(member, extract_path) + + +def load_yaml(file: str) -> dict: + """ + Loads a local YAML file and returns it as a dictionary. + """ + with open(Path(__file__).parent / file, 'r') as f: + return load(f, Loader=CLoader) diff --git a/pythonlib/camoufox/utils.py b/pythonlib/camoufox/utils.py index 752cfe0..3534d66 100644 --- a/pythonlib/camoufox/utils.py +++ b/pythonlib/camoufox/utils.py @@ -1,6 +1,5 @@ import os import sys -import warnings from os import environ from pprint import pprint from random import randrange @@ -20,7 +19,6 @@ from .addons import ( threaded_try_load_addons, ) from .exceptions import ( - DetectionWarning, InvalidOS, InvalidPropertyType, NonFirefoxFingerprint, @@ -30,6 +28,7 @@ from .fingerprints import from_browserforge, generate_fingerprint from .ip import Proxy, public_ip, valid_ipv4, valid_ipv6 from .locale import geoip_allowed, get_geolocation, normalize_locale from .pkgman import OS_NAME, get_path, installed_verstr +from .warnings import LeakWarning from .xpi_dl import add_default_addons LAUNCH_FILE = { @@ -197,12 +196,7 @@ def check_custom_fingerprint(fingerprint: Fingerprint) -> None: 'If this is intentional, pass `i_know_what_im_doing=True`.' ) - warnings.warn( - 'Passing your own fingerprint is not recommended. ' - 'BrowserForge fingerprints are automatically generated within Camoufox ' - 'based on the provided `os` and `screen` constraints.', - category=DetectionWarning, - ) + LeakWarning.warn('custom_fingerprint', False) def check_valid_os(os: ListOrString) -> None: @@ -249,6 +243,45 @@ def set_into(target: Dict[str, Any], key: str, value: Any) -> None: target[key] = value +def is_domain_set( + config: Dict[str, Any], + *properties: str, +) -> bool: + """ + Checks if a domain is set in the config. + """ + for prop in properties: + # If the . prefix exists, check if the domain is a prefix of any key in the config + if prop.endswith('.'): + if any(key.startswith(prop) for key in config): + return True + # Otherwise, check if the domain is a direct key in the config + else: + if prop in config: + return True + return False + + +def warn_manual_config(config: Dict[str, Any]) -> None: + """ + Warns the user if they are manually setting properties that Camoufox already sets internally. + """ + # Manual locale setting + if is_domain_set( + config, 'navigator.language', 'navigator.languages', 'headers.Accept-Language' + ): + LeakWarning.warn('locale', False) + # Manual User-Agent setting + if is_domain_set(config, 'headers.User-Agent'): + LeakWarning.warn('header-ua', False) + # Manual navigator setting + if is_domain_set(config, 'navigator.'): + LeakWarning.warn('navigator', False) + # Manual screen/window setting + if is_domain_set(config, 'screen.', 'window.', 'document.body.'): + LeakWarning.warn('viewport', False) + + def get_launch_options( *, config: Optional[Dict[str, Any]] = None, @@ -282,24 +315,27 @@ def get_launch_options( if config is None: config = {} + # Set default values for optional arguments if addons is None: addons = [] if args is None: args = [] if firefox_user_prefs is None: firefox_user_prefs = {} + if i_know_what_im_doing is None: + i_know_what_im_doing = False # Warn the user if headless is being used # https://github.com/daijro/camoufox/issues/26 if headless: - warnings.warn( - 'It is currently not recommended to use headless mode in Camoufox. ' - 'Some WAFs are able to detect headless browsers. The issue is currently being investigated.', - category=DetectionWarning, - ) + LeakWarning.warn('headless', i_know_what_im_doing) elif headless is None: headless = False + # Warn the user for manual config settings + if not i_know_what_im_doing: + warn_manual_config(config) + # Assert the target OS is valid if os: check_valid_os(os) @@ -366,6 +402,14 @@ def get_launch_options( geolocation = get_geolocation(geoip) config.update(geolocation.as_config()) + # Raise a warning when a proxy is being used without spoofing geolocation + elif ( + proxy + and 'localhost' not in proxy.get('server', '') + and not is_domain_set('geolocation', config) + ): + LeakWarning.warn('proxy_without_geoip') + # Set locale if locale: parsed_locale = normalize_locale(locale) @@ -391,10 +435,13 @@ def get_launch_options( if block_webrtc: firefox_user_prefs['media.peerconnection.enabled'] = False if allow_webgl: + LeakWarning.warn('allow_webgl', i_know_what_im_doing) firefox_user_prefs['webgl.disabled'] = False - # Launch + # Load the addons threaded_try_load_addons(get_debug_port(args), addons) + + # Prepare environment variables to pass to Camoufox env_vars = { **get_env_vars(config, target_os), **(cast(Dict[str, Union[str, float, bool]], environ) if env is None else env), diff --git a/pythonlib/camoufox/warnings.py b/pythonlib/camoufox/warnings.py new file mode 100644 index 0000000..5df2e87 --- /dev/null +++ b/pythonlib/camoufox/warnings.py @@ -0,0 +1,44 @@ +import inspect +import warnings +from pathlib import Path +from typing import Optional + +from camoufox.pkgman import load_yaml + +WARNINGS_DATA = load_yaml('warnings.yml') + + +class LeakWarning(RuntimeWarning): + """ + Raised when a the user has a setting enabled that can cause detection. + """ + + @staticmethod + def warn(warning_key: str, i_know_what_im_doing: Optional[bool] = None) -> None: + """ + Warns the user if a passed parameter can cause leaks. + """ + warning = WARNINGS_DATA[warning_key] + if i_know_what_im_doing: + return + if i_know_what_im_doing is not None: + warning += '\nIf this is intentional, pass `i_know_what_im_doing=True`.' + + # Get caller information + current_module = Path(__file__).parent + frame = inspect.currentframe() + while frame: + if not Path(frame.f_code.co_filename).is_relative_to(current_module): + break + frame = frame.f_back + + if frame: + warnings.warn_explicit( + warning, + category=LeakWarning, + filename=frame.f_code.co_filename, + lineno=frame.f_lineno, + ) + return + + warnings.warn(warning, category=LeakWarning) diff --git a/pythonlib/camoufox/warnings.yml b/pythonlib/camoufox/warnings.yml new file mode 100644 index 0000000..9d97183 --- /dev/null +++ b/pythonlib/camoufox/warnings.yml @@ -0,0 +1,32 @@ +headless: >- + Headless mode in Camoufox is not recommended at this time. + Some WAFs are able to detect headless browsers. The issue is currently being investigated. + +navigator: >- + Manually setting navigator properties is not recommended. + Device information is automatically generated within Camoufox + based on the provided `os`. + +locale: >- + Use the `locale` parameter in Camoufox instead of setting the config manually. + +header-ua: >- + Do not set the header.User-Agent manually. Camoufox will generate a User-Agent for you. + +viewport: >- + Manually setting screen & window properties is not recommended. + Screen dimensions are randomly generated within Camoufox + based on the provided screen constraints. See here: + https://github.com/daijro/camoufox/tree/main/pythonlib#browserforge-integration. + +custom_fingerprint: >- + Passing your own fingerprint is not recommended. + BrowserForge fingerprints are automatically generated within Camoufox + based on the provided `os` and `screen` constraints. + +proxy_without_geoip: >- + When using a proxy, it is heavily recommended that you pass `geoip=True`. + +allow_webgl: >- + Enabling WebGL can lead to Canvas fingerprinting and detection. + Camoufox will automatically spoof your vendor and renderer, but it cannot spoof your WebGL fingerprint. diff --git a/pythonlib/pyproject.toml b/pythonlib/pyproject.toml index 116ab2a..a861b1a 100644 --- a/pythonlib/pyproject.toml +++ b/pythonlib/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api" [tool.poetry] name = "camoufox" -version = "0.2.7" +version = "0.2.8" description = "Wrapper around Playwright to help launch Camoufox" authors = ["daijro "] license = "MIT"