From 966a10c045ad8861cfa903d440b73a3a11b80fe7 Mon Sep 17 00:00:00 2001 From: zhom <2717306+zhom@users.noreply.github.com> Date: Sun, 30 Nov 2025 00:27:08 +0400 Subject: [PATCH] feat: allow user select system for which to generate fingerprint --- nodecar/src/camoufox-launcher.ts | 6 ++ nodecar/src/index.ts | 8 ++ src-tauri/src/browser_runner.rs | 4 + src-tauri/src/camoufox_manager.rs | 8 ++ src/components/camoufox-config-dialog.tsx | 18 +++- src/components/create-profile-dialog.tsx | 17 +++- .../shared-camoufox-config-form.tsx | 90 ++++++++++++++++++- src/types.ts | 3 + 8 files changed, 146 insertions(+), 8 deletions(-) diff --git a/nodecar/src/camoufox-launcher.ts b/nodecar/src/camoufox-launcher.ts index 153a811..e3c9677 100644 --- a/nodecar/src/camoufox-launcher.ts +++ b/nodecar/src/camoufox-launcher.ts @@ -352,6 +352,7 @@ interface GenerateConfigOptions { blockWebgl?: boolean; executablePath?: string; fingerprint?: string; + os?: "windows" | "macos" | "linux"; } /** @@ -433,6 +434,11 @@ export async function generateCamoufoxConfig( launchOpts.allowAddonNewTab = true; + // Add OS option for fingerprint generation + if (options.os) { + launchOpts.os = options.os; + } + // Generate the configuration using launchOptions const generatedOptions = await launchOptions(launchOpts); diff --git a/nodecar/src/index.ts b/nodecar/src/index.ts index e4b1463..eb43ac3 100644 --- a/nodecar/src/index.ts +++ b/nodecar/src/index.ts @@ -34,6 +34,10 @@ program .option("--fingerprint ", "fingerprint JSON string") .option("--headless", "run in headless mode") .option("--custom-config ", "custom config JSON string") + .option( + "--os ", + "operating system for fingerprint: windows, macos, linux", + ) .description("manage Camoufox browser instances") .action( @@ -284,6 +288,10 @@ program typeof options.fingerprint === "string" ? options.fingerprint : undefined, + os: + typeof options.os === "string" + ? (options.os as "windows" | "macos" | "linux") + : undefined, }); console.log(config); process.exit(0); diff --git a/src-tauri/src/browser_runner.rs b/src-tauri/src/browser_runner.rs index facd196..4b1b370 100644 --- a/src-tauri/src/browser_runner.rs +++ b/src-tauri/src/browser_runner.rs @@ -214,6 +214,10 @@ impl BrowserRunner { updated_camoufox_config.fingerprint = Some(new_fingerprint); // Preserve the randomize flag so it persists across launches updated_camoufox_config.randomize_fingerprint_on_launch = Some(true); + // Preserve the OS setting so it's used for future fingerprint generation + if camoufox_config.os.is_some() { + updated_camoufox_config.os = camoufox_config.os.clone(); + } updated_profile.camoufox_config = Some(updated_camoufox_config.clone()); log::info!( diff --git a/src-tauri/src/camoufox_manager.rs b/src-tauri/src/camoufox_manager.rs index e6fe115..09d161d 100644 --- a/src-tauri/src/camoufox_manager.rs +++ b/src-tauri/src/camoufox_manager.rs @@ -23,6 +23,7 @@ pub struct CamoufoxConfig { pub executable_path: Option, pub fingerprint: Option, // JSON string of the complete fingerprint config pub randomize_fingerprint_on_launch: Option, // Generate new fingerprint on every launch + pub os: Option, // Operating system for fingerprint generation: "windows", "macos", or "linux" } impl Default for CamoufoxConfig { @@ -40,6 +41,7 @@ impl Default for CamoufoxConfig { executable_path: None, fingerprint: None, randomize_fingerprint_on_launch: None, + os: None, } } } @@ -171,6 +173,11 @@ impl CamoufoxManager { } } + // Add OS option for fingerprint generation + if let Some(os) = &config.os { + config_args.extend(["--os".to_string(), os.clone()]); + } + // Execute config generation command let mut config_sidecar = self.get_nodecar_sidecar(app_handle)?; for arg in &config_args { @@ -496,6 +503,7 @@ mod tests { assert_eq!(default_config.proxy, None); assert_eq!(default_config.fingerprint, None); assert_eq!(default_config.randomize_fingerprint_on_launch, None); + assert_eq!(default_config.os, None); } } diff --git a/src/components/camoufox-config-dialog.tsx b/src/components/camoufox-config-dialog.tsx index 57d3c6c..cda7d12 100644 --- a/src/components/camoufox-config-dialog.tsx +++ b/src/components/camoufox-config-dialog.tsx @@ -10,7 +10,16 @@ import { DialogTitle, } from "@/components/ui/dialog"; import { ScrollArea } from "@/components/ui/scroll-area"; -import type { BrowserProfile, CamoufoxConfig } from "@/types"; +import type { BrowserProfile, CamoufoxConfig, CamoufoxOS } from "@/types"; + +const getCurrentOS = (): CamoufoxOS => { + if (typeof navigator === "undefined") return "linux"; + const platform = navigator.platform.toLowerCase(); + if (platform.includes("win")) return "windows"; + if (platform.includes("mac")) return "macos"; + return "linux"; +}; + import { LoadingButton } from "./loading-button"; import { RippleButton } from "./ui/ripple"; @@ -29,9 +38,10 @@ export function CamoufoxConfigDialog({ onSave, isRunning = false, }: CamoufoxConfigDialogProps) { - const [config, setConfig] = useState({ + const [config, setConfig] = useState(() => ({ geoip: true, - }); + os: getCurrentOS(), + })); const [isSaving, setIsSaving] = useState(false); // Initialize config when profile changes @@ -40,6 +50,7 @@ export function CamoufoxConfigDialog({ setConfig( profile.camoufox_config || { geoip: true, + os: getCurrentOS(), }, ); } @@ -88,6 +99,7 @@ export function CamoufoxConfigDialog({ setConfig( profile.camoufox_config || { geoip: true, + os: getCurrentOS(), }, ); } diff --git a/src/components/create-profile-dialog.tsx b/src/components/create-profile-dialog.tsx index 97d702c..cd9afe5 100644 --- a/src/components/create-profile-dialog.tsx +++ b/src/components/create-profile-dialog.tsx @@ -29,7 +29,16 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { useBrowserDownload } from "@/hooks/use-browser-download"; import { useProxyEvents } from "@/hooks/use-proxy-events"; import { getBrowserIcon } from "@/lib/browser-utils"; -import type { BrowserReleaseTypes, CamoufoxConfig } from "@/types"; +import type { BrowserReleaseTypes, CamoufoxConfig, CamoufoxOS } from "@/types"; + +const getCurrentOS = (): CamoufoxOS => { + if (typeof navigator === "undefined") return "linux"; + const platform = navigator.platform.toLowerCase(); + if (platform.includes("win")) return "windows"; + if (platform.includes("mac")) return "macos"; + return "linux"; +}; + import { RippleButton } from "./ui/ripple"; type BrowserTypeString = @@ -111,9 +120,10 @@ export function CreateProfileDialog({ const [selectedProxyId, setSelectedProxyId] = useState(); // Camoufox anti-detect states - const [camoufoxConfig, setCamoufoxConfig] = useState({ + const [camoufoxConfig, setCamoufoxConfig] = useState(() => ({ geoip: true, // Default to automatic geoip - }); + os: getCurrentOS(), // Default to current OS + })); // Handle browser selection from the initial screen const handleBrowserSelect = (browser: BrowserTypeString) => { @@ -379,6 +389,7 @@ export function CreateProfileDialog({ setReleaseTypes({}); setCamoufoxConfig({ geoip: true, // Reset to automatic geoip + os: getCurrentOS(), // Reset to current OS }); onClose(); }; diff --git a/src/components/shared-camoufox-config-form.tsx b/src/components/shared-camoufox-config-form.tsx index be836e5..434aeeb 100644 --- a/src/components/shared-camoufox-config-form.tsx +++ b/src/components/shared-camoufox-config-form.tsx @@ -15,7 +15,11 @@ import { } from "@/components/ui/select"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Textarea } from "@/components/ui/textarea"; -import type { CamoufoxConfig, CamoufoxFingerprintConfig } from "@/types"; +import type { + CamoufoxConfig, + CamoufoxFingerprintConfig, + CamoufoxOS, +} from "@/types"; interface SharedCamoufoxConfigFormProps { config: CamoufoxConfig; @@ -31,6 +35,22 @@ const isFingerprintEditingDisabled = (config: CamoufoxConfig): boolean => { return config.randomize_fingerprint_on_launch === true; }; +// Detect the current operating system +const getCurrentOS = (): CamoufoxOS => { + if (typeof navigator === "undefined") return "linux"; + const platform = navigator.platform.toLowerCase(); + if (platform.includes("win")) return "windows"; + if (platform.includes("mac")) return "macos"; + return "linux"; +}; + +// OS display labels +const osLabels: Record = { + windows: "Windows", + macos: "macOS", + linux: "Linux", +}; + // Component for editing nested objects like webGl:parameters interface ObjectEditorProps { value: Record | undefined; @@ -102,6 +122,11 @@ export function SharedCamoufoxConfigForm({ ); const [fingerprintConfig, setFingerprintConfig] = useState({}); + const [currentOS] = useState(getCurrentOS); + + // Get selected OS (defaults to current OS) + const selectedOS = config.os || currentOS; + const isOSDifferent = selectedOS !== currentOS; // Set screen resolution to user's screen size when creating a new profile useEffect(() => { @@ -188,6 +213,35 @@ export function SharedCamoufoxConfigForm({ const renderAdvancedForm = () => (
+ {/* Operating System Selection */} +
+ + + {isOSDifferent && ( + + + ⚠️ Warning: Selecting an OS different from your current system ( + {osLabels[currentOS]}) increases the risk of detection. Websites + can detect mismatches between your fingerprint and actual system + behavior. + + + )} +
+ {/* Randomize Fingerprint Option */}
@@ -906,8 +960,40 @@ export function SharedCamoufoxConfigForm({ + {/* Operating System Selection */} +
+ + + {isOSDifferent && ( + + + ⚠️ Warning: Selecting an OS different from your current + system ({osLabels[currentOS]}) increases the risk of + detection. Websites with advanced protections can detect + mismatches between your fingerprint and actual system + behavior. + + + )} +
+ {/* Randomize Fingerprint Option */} -
+