fix: randomize hardware fingerprint values across browser sessions
Replace static stealthChromiumScripts and stealthFirefoxScripts slices with builder functions that accept hardware profile structs. Each browser session now randomly selects from a pool of 6 realistic profiles per engine, and Chromium connection stats receive per-session jitter (±20ms RTT, ±2 Mbps downlink). This prevents anti-bot systems from correlating sessions via identical WebGL, connection, mozInnerScreen, and hardwareConcurrency fingerprints. Closes #71 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
134
stealth_test.go
134
stealth_test.go
@@ -129,14 +129,16 @@ func TestStealthCommonScripts_Notification(t *testing.T) {
|
||||
// --- Chromium scripts ---
|
||||
|
||||
func TestStealthChromiumScripts_Count(t *testing.T) {
|
||||
if len(stealthChromiumScripts) != 8 {
|
||||
t.Fatalf("expected 8 chromium stealth scripts, got %d", len(stealthChromiumScripts))
|
||||
scripts := buildChromiumStealthScripts(chromiumHWProfiles[0])
|
||||
if len(scripts) != 8 {
|
||||
t.Fatalf("expected 8 chromium stealth scripts, got %d", len(scripts))
|
||||
}
|
||||
}
|
||||
|
||||
func TestStealthChromiumScripts_Plugins(t *testing.T) {
|
||||
scripts := buildChromiumStealthScripts(chromiumHWProfiles[0])
|
||||
found := false
|
||||
for _, s := range stealthChromiumScripts {
|
||||
for _, s := range scripts {
|
||||
if strings.Contains(s, "Chrome PDF Plugin") && strings.Contains(s, "navigator") && strings.Contains(s, "plugins") {
|
||||
found = true
|
||||
break
|
||||
@@ -148,8 +150,9 @@ func TestStealthChromiumScripts_Plugins(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStealthChromiumScripts_MimeTypes(t *testing.T) {
|
||||
scripts := buildChromiumStealthScripts(chromiumHWProfiles[0])
|
||||
found := false
|
||||
for _, s := range stealthChromiumScripts {
|
||||
for _, s := range scripts {
|
||||
if strings.Contains(s, "mimeTypes") && strings.Contains(s, "application/pdf") {
|
||||
found = true
|
||||
break
|
||||
@@ -161,8 +164,9 @@ func TestStealthChromiumScripts_MimeTypes(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStealthChromiumScripts_WindowChrome(t *testing.T) {
|
||||
scripts := buildChromiumStealthScripts(chromiumHWProfiles[0])
|
||||
found := false
|
||||
for _, s := range stealthChromiumScripts {
|
||||
for _, s := range scripts {
|
||||
if strings.Contains(s, "window.chrome") && strings.Contains(s, "runtime") {
|
||||
found = true
|
||||
break
|
||||
@@ -174,8 +178,9 @@ func TestStealthChromiumScripts_WindowChrome(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStealthChromiumScripts_ChromeApp(t *testing.T) {
|
||||
scripts := buildChromiumStealthScripts(chromiumHWProfiles[0])
|
||||
found := false
|
||||
for _, s := range stealthChromiumScripts {
|
||||
for _, s := range scripts {
|
||||
if strings.Contains(s, "chrome.app") && strings.Contains(s, "chrome.csi") && strings.Contains(s, "chrome.loadTimes") {
|
||||
found = true
|
||||
break
|
||||
@@ -187,8 +192,9 @@ func TestStealthChromiumScripts_ChromeApp(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStealthChromiumScripts_WebGLSpoof(t *testing.T) {
|
||||
scripts := buildChromiumStealthScripts(chromiumHWProfiles[0])
|
||||
found := false
|
||||
for _, s := range stealthChromiumScripts {
|
||||
for _, s := range scripts {
|
||||
if strings.Contains(s, "37446") && strings.Contains(s, "ANGLE") {
|
||||
found = true
|
||||
break
|
||||
@@ -200,8 +206,9 @@ func TestStealthChromiumScripts_WebGLSpoof(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStealthChromiumScripts_NavigatorConnection(t *testing.T) {
|
||||
scripts := buildChromiumStealthScripts(chromiumHWProfiles[0])
|
||||
found := false
|
||||
for _, s := range stealthChromiumScripts {
|
||||
for _, s := range scripts {
|
||||
if strings.Contains(s, "connection") && strings.Contains(s, "effectiveType") {
|
||||
found = true
|
||||
break
|
||||
@@ -213,8 +220,9 @@ func TestStealthChromiumScripts_NavigatorConnection(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStealthChromiumScripts_CDPCleanup(t *testing.T) {
|
||||
scripts := buildChromiumStealthScripts(chromiumHWProfiles[0])
|
||||
found := false
|
||||
for _, s := range stealthChromiumScripts {
|
||||
for _, s := range scripts {
|
||||
if strings.Contains(s, "cdc_") && strings.Contains(s, "delete") {
|
||||
found = true
|
||||
break
|
||||
@@ -226,8 +234,9 @@ func TestStealthChromiumScripts_CDPCleanup(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStealthChromiumScripts_UserAgentStrip(t *testing.T) {
|
||||
scripts := buildChromiumStealthScripts(chromiumHWProfiles[0])
|
||||
found := false
|
||||
for _, s := range stealthChromiumScripts {
|
||||
for _, s := range scripts {
|
||||
if strings.Contains(s, "HeadlessChrome") && strings.Contains(s, "userAgent") {
|
||||
found = true
|
||||
break
|
||||
@@ -241,14 +250,16 @@ func TestStealthChromiumScripts_UserAgentStrip(t *testing.T) {
|
||||
// --- Firefox scripts ---
|
||||
|
||||
func TestStealthFirefoxScripts_Count(t *testing.T) {
|
||||
if len(stealthFirefoxScripts) != 5 {
|
||||
t.Fatalf("expected 5 firefox stealth scripts, got %d", len(stealthFirefoxScripts))
|
||||
scripts := buildFirefoxStealthScripts(firefoxHWProfiles[0])
|
||||
if len(scripts) != 5 {
|
||||
t.Fatalf("expected 5 firefox stealth scripts, got %d", len(scripts))
|
||||
}
|
||||
}
|
||||
|
||||
func TestStealthFirefoxScripts_WebdriverHardening(t *testing.T) {
|
||||
scripts := buildFirefoxStealthScripts(firefoxHWProfiles[0])
|
||||
found := false
|
||||
for _, s := range stealthFirefoxScripts {
|
||||
for _, s := range scripts {
|
||||
if strings.Contains(s, "getOwnPropertyDescriptor") && strings.Contains(s, "webdriver") {
|
||||
found = true
|
||||
break
|
||||
@@ -260,8 +271,9 @@ func TestStealthFirefoxScripts_WebdriverHardening(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStealthFirefoxScripts_WebGLSpoof(t *testing.T) {
|
||||
scripts := buildFirefoxStealthScripts(firefoxHWProfiles[0])
|
||||
found := false
|
||||
for _, s := range stealthFirefoxScripts {
|
||||
for _, s := range scripts {
|
||||
if strings.Contains(s, "37446") && strings.Contains(s, "Mesa DRI") {
|
||||
found = true
|
||||
break
|
||||
@@ -273,8 +285,9 @@ func TestStealthFirefoxScripts_WebGLSpoof(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStealthFirefoxScripts_MozInnerScreen(t *testing.T) {
|
||||
scripts := buildFirefoxStealthScripts(firefoxHWProfiles[0])
|
||||
found := false
|
||||
for _, s := range stealthFirefoxScripts {
|
||||
for _, s := range scripts {
|
||||
if strings.Contains(s, "mozInnerScreenX") && strings.Contains(s, "mozInnerScreenY") {
|
||||
found = true
|
||||
break
|
||||
@@ -286,8 +299,9 @@ func TestStealthFirefoxScripts_MozInnerScreen(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStealthFirefoxScripts_HardwareConcurrency(t *testing.T) {
|
||||
scripts := buildFirefoxStealthScripts(firefoxHWProfiles[0])
|
||||
found := false
|
||||
for _, s := range stealthFirefoxScripts {
|
||||
for _, s := range scripts {
|
||||
if strings.Contains(s, "hardwareConcurrency") {
|
||||
found = true
|
||||
break
|
||||
@@ -299,8 +313,9 @@ func TestStealthFirefoxScripts_HardwareConcurrency(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStealthFirefoxScripts_PDFjsPlugins(t *testing.T) {
|
||||
scripts := buildFirefoxStealthScripts(firefoxHWProfiles[0])
|
||||
found := false
|
||||
for _, s := range stealthFirefoxScripts {
|
||||
for _, s := range scripts {
|
||||
if strings.Contains(s, "PDF.js") && strings.Contains(s, "plugins") {
|
||||
found = true
|
||||
break
|
||||
@@ -314,17 +329,19 @@ func TestStealthFirefoxScripts_PDFjsPlugins(t *testing.T) {
|
||||
// --- Cross-category validation ---
|
||||
|
||||
func TestStealthScripts_NoOverlap(t *testing.T) {
|
||||
chromiumScripts := buildChromiumStealthScripts(chromiumHWProfiles[0])
|
||||
firefoxScripts := buildFirefoxStealthScripts(firefoxHWProfiles[0])
|
||||
all := make(map[string]string) // script -> category
|
||||
for _, s := range stealthCommonScripts {
|
||||
all[s] = "common"
|
||||
}
|
||||
for _, s := range stealthChromiumScripts {
|
||||
for _, s := range chromiumScripts {
|
||||
if cat, ok := all[s]; ok {
|
||||
t.Fatalf("chromium script also appears in %s category", cat)
|
||||
}
|
||||
all[s] = "chromium"
|
||||
}
|
||||
for _, s := range stealthFirefoxScripts {
|
||||
for _, s := range firefoxScripts {
|
||||
if cat, ok := all[s]; ok {
|
||||
t.Fatalf("firefox script also appears in %s category", cat)
|
||||
}
|
||||
@@ -354,8 +371,9 @@ func TestStealthCommonScripts_NoFirefoxMarkers(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStealthChromiumScripts_NoFirefoxMarkers(t *testing.T) {
|
||||
scripts := buildChromiumStealthScripts(chromiumHWProfiles[0])
|
||||
firefoxMarkers := []string{"mozInnerScreen", "Mesa DRI", "PDF.js"}
|
||||
for _, s := range stealthChromiumScripts {
|
||||
for _, s := range scripts {
|
||||
for _, marker := range firefoxMarkers {
|
||||
if strings.Contains(s, marker) {
|
||||
t.Fatalf("chromium script contains Firefox-specific marker %q", marker)
|
||||
@@ -365,8 +383,9 @@ func TestStealthChromiumScripts_NoFirefoxMarkers(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStealthFirefoxScripts_NoChromiumMarkers(t *testing.T) {
|
||||
scripts := buildFirefoxStealthScripts(firefoxHWProfiles[0])
|
||||
chromiumMarkers := []string{"window.chrome", "chrome.app", "chrome.csi", "chrome.loadTimes", "HeadlessChrome", "cdc_", "Chrome PDF Plugin", "ANGLE"}
|
||||
for _, s := range stealthFirefoxScripts {
|
||||
for _, s := range scripts {
|
||||
for _, marker := range chromiumMarkers {
|
||||
if strings.Contains(s, marker) {
|
||||
t.Fatalf("firefox script contains Chromium-specific marker %q", marker)
|
||||
@@ -439,3 +458,76 @@ func TestMergeOptions_ExplicitUAPreserved(t *testing.T) {
|
||||
t.Fatalf("expected explicit UA %q preserved, got %q", customUA, got.UserAgent)
|
||||
}
|
||||
}
|
||||
|
||||
// --- Hardware profile pools ---
|
||||
|
||||
func TestChromiumHWProfiles_NotEmpty(t *testing.T) {
|
||||
if len(chromiumHWProfiles) < 2 {
|
||||
t.Fatalf("expected at least 2 chromium hardware profiles, got %d", len(chromiumHWProfiles))
|
||||
}
|
||||
}
|
||||
|
||||
func TestFirefoxHWProfiles_NotEmpty(t *testing.T) {
|
||||
if len(firefoxHWProfiles) < 2 {
|
||||
t.Fatalf("expected at least 2 firefox hardware profiles, got %d", len(firefoxHWProfiles))
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildChromiumStealthScripts_ProfileValues(t *testing.T) {
|
||||
p := chromiumHWProfiles[1] // NVIDIA profile
|
||||
scripts := buildChromiumStealthScripts(p)
|
||||
joined := strings.Join(scripts, "\n")
|
||||
if !strings.Contains(joined, p.WebGLVendor) {
|
||||
t.Fatalf("expected chromium scripts to contain vendor %q", p.WebGLVendor)
|
||||
}
|
||||
if !strings.Contains(joined, p.WebGLRenderer) {
|
||||
t.Fatalf("expected chromium scripts to contain renderer %q", p.WebGLRenderer)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildFirefoxStealthScripts_ProfileValues(t *testing.T) {
|
||||
p := firefoxHWProfiles[2] // AMD profile
|
||||
scripts := buildFirefoxStealthScripts(p)
|
||||
joined := strings.Join(scripts, "\n")
|
||||
if !strings.Contains(joined, p.WebGLVendor) {
|
||||
t.Fatalf("expected firefox scripts to contain vendor %q", p.WebGLVendor)
|
||||
}
|
||||
if !strings.Contains(joined, p.WebGLRenderer) {
|
||||
t.Fatalf("expected firefox scripts to contain renderer %q", p.WebGLRenderer)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildChromiumStealthScripts_ConnectionJitter(t *testing.T) {
|
||||
p := chromiumHWProfiles[0]
|
||||
seen := make(map[string]bool)
|
||||
for range 50 {
|
||||
scripts := buildChromiumStealthScripts(p)
|
||||
// The connection script is at index 5.
|
||||
seen[scripts[5]] = true
|
||||
}
|
||||
if len(seen) < 2 {
|
||||
t.Fatal("expected connection script to vary across calls due to jitter, but all 50 were identical")
|
||||
}
|
||||
}
|
||||
|
||||
func TestChromiumHWProfiles_NoSingleQuotes(t *testing.T) {
|
||||
for i, p := range chromiumHWProfiles {
|
||||
if strings.Contains(p.WebGLVendor, "'") {
|
||||
t.Fatalf("chromium profile %d vendor contains single quote (breaks JS)", i)
|
||||
}
|
||||
if strings.Contains(p.WebGLRenderer, "'") {
|
||||
t.Fatalf("chromium profile %d renderer contains single quote (breaks JS)", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFirefoxHWProfiles_NoSingleQuotes(t *testing.T) {
|
||||
for i, p := range firefoxHWProfiles {
|
||||
if strings.Contains(p.WebGLVendor, "'") {
|
||||
t.Fatalf("firefox profile %d vendor contains single quote (breaks JS)", i)
|
||||
}
|
||||
if strings.Contains(p.WebGLRenderer, "'") {
|
||||
t.Fatalf("firefox profile %d renderer contains single quote (breaks JS)", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user