Merge pull request 'fix: randomize hardware fingerprints across sessions' (#74) from feature/71-randomize-fingerprints into main
All checks were successful
CI / vet (push) Successful in 48s
CI / test (push) Successful in 53s
CI / build (push) Successful in 57s

Reviewed-on: #74
This commit was merged in pull request #74.
This commit is contained in:
2026-02-24 01:39:27 +00:00
3 changed files with 233 additions and 70 deletions

View File

@@ -74,9 +74,9 @@ func initBrowser(opt BrowserOptions) (*browserInitResult, error) {
initScripts = append(initScripts, stealthCommonScripts...) initScripts = append(initScripts, stealthCommonScripts...)
switch opt.Browser { switch opt.Browser {
case BrowserChromium: case BrowserChromium:
initScripts = append(initScripts, stealthChromiumScripts...) initScripts = append(initScripts, buildChromiumStealthScripts(randomChromiumProfile())...)
case BrowserFirefox: case BrowserFirefox:
initScripts = append(initScripts, stealthFirefoxScripts...) initScripts = append(initScripts, buildFirefoxStealthScripts(randomFirefoxProfile())...)
} }
} }

View File

@@ -1,5 +1,10 @@
package extractor package extractor
import (
"fmt"
"math/rand/v2"
)
// stealthChromiumArgs are launch arguments that reduce automation detection for Chromium-based browsers. // stealthChromiumArgs are launch arguments that reduce automation detection for Chromium-based browsers.
var stealthChromiumArgs = []string{ var stealthChromiumArgs = []string{
"--disable-blink-features=AutomationControlled", "--disable-blink-features=AutomationControlled",
@@ -39,8 +44,45 @@ var stealthCommonScripts = []string{
})()`, })()`,
} }
// stealthChromiumScripts are JavaScript snippets specific to Chromium-based browsers. // chromiumHWProfile holds hardware fingerprint values for a Chromium browser session.
var stealthChromiumScripts = []string{ type chromiumHWProfile struct {
WebGLVendor string
WebGLRenderer string
ConnRTT int // base RTT in ms (jittered ±20 per session)
ConnDownlink float64 // base downlink in Mbps (jittered ±2 per session)
}
// chromiumHWProfiles is a pool of realistic Chromium hardware profiles.
// Index 0 matches the original hardcoded values.
var chromiumHWProfiles = []chromiumHWProfile{
{"Google Inc. (Intel)", "ANGLE (Intel, Intel(R) UHD Graphics 630, OpenGL 4.5)", 50, 10},
{"Google Inc. (NVIDIA)", "ANGLE (NVIDIA, NVIDIA GeForce GTX 1660 SUPER, D3D11)", 30, 25},
{"Google Inc. (AMD)", "ANGLE (AMD, AMD Radeon RX 580, D3D11)", 100, 5},
{"Google Inc. (Intel)", "ANGLE (Intel, Intel(R) UHD Graphics 770, OpenGL 4.5)", 50, 10},
{"Google Inc. (NVIDIA)", "ANGLE (NVIDIA, NVIDIA GeForce RTX 3060, D3D11)", 25, 50},
{"Google Inc. (Intel)", "ANGLE (Intel, Intel(R) Iris Xe Graphics, D3D11)", 75, 8},
}
// randomChromiumProfile returns a randomly selected Chromium hardware profile.
func randomChromiumProfile() chromiumHWProfile {
return chromiumHWProfiles[rand.IntN(len(chromiumHWProfiles))]
}
// buildChromiumStealthScripts returns Chromium stealth init scripts with the given hardware profile
// values templated into the WebGL and connection spoofing scripts. Connection RTT and downlink
// receive per-session jitter (±20ms RTT, ±2 Mbps downlink).
func buildChromiumStealthScripts(p chromiumHWProfile) []string {
// Apply jitter to connection stats.
rtt := p.ConnRTT + rand.IntN(41) - 20 // ±20ms
if rtt < 0 {
rtt = 0
}
downlink := p.ConnDownlink + (rand.Float64()*4 - 2) // ±2 Mbps
if downlink < 0.5 {
downlink = 0.5
}
return []string{
// Populate navigator.plugins with realistic Chromium entries so plugins.length > 0. // Populate navigator.plugins with realistic Chromium entries so plugins.length > 0.
`Object.defineProperty(navigator, 'plugins', { `Object.defineProperty(navigator, 'plugins', {
get: () => { get: () => {
@@ -88,33 +130,33 @@ var stealthChromiumScripts = []string{
})()`, })()`,
// Spoof WebGL renderer to hide SwiftShader (headless GPU) fingerprint with Chromium ANGLE strings. // Spoof WebGL renderer to hide SwiftShader (headless GPU) fingerprint with Chromium ANGLE strings.
`(function() { fmt.Sprintf(`(function() {
const getParam = WebGLRenderingContext.prototype.getParameter; const getParam = WebGLRenderingContext.prototype.getParameter;
WebGLRenderingContext.prototype.getParameter = function(param) { WebGLRenderingContext.prototype.getParameter = function(param) {
if (param === 37445) return 'Google Inc. (Intel)'; if (param === 37445) return '%s';
if (param === 37446) return 'ANGLE (Intel, Intel(R) UHD Graphics 630, OpenGL 4.5)'; if (param === 37446) return '%s';
return getParam.call(this, param); return getParam.call(this, param);
}; };
if (typeof WebGL2RenderingContext !== 'undefined') { if (typeof WebGL2RenderingContext !== 'undefined') {
const getParam2 = WebGL2RenderingContext.prototype.getParameter; const getParam2 = WebGL2RenderingContext.prototype.getParameter;
WebGL2RenderingContext.prototype.getParameter = function(param) { WebGL2RenderingContext.prototype.getParameter = function(param) {
if (param === 37445) return 'Google Inc. (Intel)'; if (param === 37445) return '%s';
if (param === 37446) return 'ANGLE (Intel, Intel(R) UHD Graphics 630, OpenGL 4.5)'; if (param === 37446) return '%s';
return getParam2.call(this, param); return getParam2.call(this, param);
}; };
} }
})()`, })()`, p.WebGLVendor, p.WebGLRenderer, p.WebGLVendor, p.WebGLRenderer),
// Stub navigator.connection (Network Information API) if missing (Chrome-only API). // Stub navigator.connection (Network Information API) if missing (Chrome-only API).
`(function() { fmt.Sprintf(`(function() {
if (!navigator.connection) { if (!navigator.connection) {
Object.defineProperty(navigator, 'connection', { Object.defineProperty(navigator, 'connection', {
get: function() { get: function() {
return { effectiveType: '4g', rtt: 50, downlink: 10, saveData: false, onchange: null }; return { effectiveType: '4g', rtt: %d, downlink: %.1f, saveData: false, onchange: null };
}, },
}); });
} }
})()`, })()`, rtt, downlink),
// Remove CDP artifacts (window.cdc_* globals injected by Chrome DevTools Protocol). // Remove CDP artifacts (window.cdc_* globals injected by Chrome DevTools Protocol).
`(function() { `(function() {
@@ -135,9 +177,37 @@ var stealthChromiumScripts = []string{
} }
})()`, })()`,
} }
}
// stealthFirefoxScripts are JavaScript snippets specific to Firefox. // firefoxHWProfile holds hardware fingerprint values for a Firefox browser session.
var stealthFirefoxScripts = []string{ type firefoxHWProfile struct {
WebGLVendor string
WebGLRenderer string
MozInnerScreenX int
MozInnerScreenY int
HardwareConcurrency int
}
// firefoxHWProfiles is a pool of realistic Firefox hardware profiles.
// Index 0 matches the original hardcoded values.
var firefoxHWProfiles = []firefoxHWProfile{
{"Intel Open Source Technology Center", "Mesa DRI Intel(R) UHD Graphics 630", 8, 51, 4},
{"Intel Open Source Technology Center", "Mesa DRI Intel(R) HD Graphics 530", 0, 71, 8},
{"X.Org", "AMD Radeon RX 580 (polaris10, LLVM 15.0.7, DRM 3.49, 6.1.0-18-amd64)", 8, 51, 8},
{"Intel Open Source Technology Center", "Mesa DRI Intel(R) UHD Graphics 770", 0, 51, 16},
{"nouveau", "NV167", 8, 71, 4},
{"Intel", "Mesa Intel(R) Iris(R) Xe Graphics", 0, 51, 8},
}
// randomFirefoxProfile returns a randomly selected Firefox hardware profile.
func randomFirefoxProfile() firefoxHWProfile {
return firefoxHWProfiles[rand.IntN(len(firefoxHWProfiles))]
}
// buildFirefoxStealthScripts returns Firefox stealth init scripts with the given hardware profile
// values templated into the WebGL, mozInnerScreen, and hardwareConcurrency spoofing scripts.
func buildFirefoxStealthScripts(p firefoxHWProfile) []string {
return []string{
// Harden navigator.webdriver for Firefox: ensure Object.getOwnPropertyDescriptor also returns undefined. // Harden navigator.webdriver for Firefox: ensure Object.getOwnPropertyDescriptor also returns undefined.
`(function() { `(function() {
const proto = Object.getPrototypeOf(navigator); const proto = Object.getPrototypeOf(navigator);
@@ -150,40 +220,40 @@ var stealthFirefoxScripts = []string{
}; };
})()`, })()`,
// Spoof WebGL renderer with Firefox-appropriate Mesa/Intel strings. // Spoof WebGL renderer with Firefox-appropriate Mesa/driver strings.
`(function() { fmt.Sprintf(`(function() {
const getParam = WebGLRenderingContext.prototype.getParameter; const getParam = WebGLRenderingContext.prototype.getParameter;
WebGLRenderingContext.prototype.getParameter = function(param) { WebGLRenderingContext.prototype.getParameter = function(param) {
if (param === 37445) return 'Intel Open Source Technology Center'; if (param === 37445) return '%s';
if (param === 37446) return 'Mesa DRI Intel(R) UHD Graphics 630'; if (param === 37446) return '%s';
return getParam.call(this, param); return getParam.call(this, param);
}; };
if (typeof WebGL2RenderingContext !== 'undefined') { if (typeof WebGL2RenderingContext !== 'undefined') {
const getParam2 = WebGL2RenderingContext.prototype.getParameter; const getParam2 = WebGL2RenderingContext.prototype.getParameter;
WebGL2RenderingContext.prototype.getParameter = function(param) { WebGL2RenderingContext.prototype.getParameter = function(param) {
if (param === 37445) return 'Intel Open Source Technology Center'; if (param === 37445) return '%s';
if (param === 37446) return 'Mesa DRI Intel(R) UHD Graphics 630'; if (param === 37446) return '%s';
return getParam2.call(this, param); return getParam2.call(this, param);
}; };
} }
})()`, })()`, p.WebGLVendor, p.WebGLRenderer, p.WebGLVendor, p.WebGLRenderer),
// Spoof mozInnerScreenX/mozInnerScreenY which are 0 in headless Firefox. // Spoof mozInnerScreenX/mozInnerScreenY which are 0 in headless Firefox.
`(function() { fmt.Sprintf(`(function() {
if (window.mozInnerScreenX === 0) { if (window.mozInnerScreenX === 0) {
Object.defineProperty(window, 'mozInnerScreenX', { get: () => 8 }); Object.defineProperty(window, 'mozInnerScreenX', { get: () => %d });
} }
if (window.mozInnerScreenY === 0) { if (window.mozInnerScreenY === 0) {
Object.defineProperty(window, 'mozInnerScreenY', { get: () => 51 }); Object.defineProperty(window, 'mozInnerScreenY', { get: () => %d });
} }
})()`, })()`, p.MozInnerScreenX, p.MozInnerScreenY),
// Normalize navigator.hardwareConcurrency (Firefox headless sometimes reports 2). // Normalize navigator.hardwareConcurrency (Firefox headless sometimes reports 2).
`(function() { fmt.Sprintf(`(function() {
if (navigator.hardwareConcurrency <= 2) { if (navigator.hardwareConcurrency <= 2) {
Object.defineProperty(navigator, 'hardwareConcurrency', { get: () => 4 }); Object.defineProperty(navigator, 'hardwareConcurrency', { get: () => %d });
} }
})()`, })()`, p.HardwareConcurrency),
// Override navigator.plugins with Firefox-appropriate PDF.js entry. // Override navigator.plugins with Firefox-appropriate PDF.js entry.
`Object.defineProperty(navigator, 'plugins', { `Object.defineProperty(navigator, 'plugins', {
@@ -198,3 +268,4 @@ var stealthFirefoxScripts = []string{
}, },
})`, })`,
} }
}

View File

@@ -129,14 +129,16 @@ func TestStealthCommonScripts_Notification(t *testing.T) {
// --- Chromium scripts --- // --- Chromium scripts ---
func TestStealthChromiumScripts_Count(t *testing.T) { func TestStealthChromiumScripts_Count(t *testing.T) {
if len(stealthChromiumScripts) != 8 { scripts := buildChromiumStealthScripts(chromiumHWProfiles[0])
t.Fatalf("expected 8 chromium stealth scripts, got %d", len(stealthChromiumScripts)) if len(scripts) != 8 {
t.Fatalf("expected 8 chromium stealth scripts, got %d", len(scripts))
} }
} }
func TestStealthChromiumScripts_Plugins(t *testing.T) { func TestStealthChromiumScripts_Plugins(t *testing.T) {
scripts := buildChromiumStealthScripts(chromiumHWProfiles[0])
found := false 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") { if strings.Contains(s, "Chrome PDF Plugin") && strings.Contains(s, "navigator") && strings.Contains(s, "plugins") {
found = true found = true
break break
@@ -148,8 +150,9 @@ func TestStealthChromiumScripts_Plugins(t *testing.T) {
} }
func TestStealthChromiumScripts_MimeTypes(t *testing.T) { func TestStealthChromiumScripts_MimeTypes(t *testing.T) {
scripts := buildChromiumStealthScripts(chromiumHWProfiles[0])
found := false found := false
for _, s := range stealthChromiumScripts { for _, s := range scripts {
if strings.Contains(s, "mimeTypes") && strings.Contains(s, "application/pdf") { if strings.Contains(s, "mimeTypes") && strings.Contains(s, "application/pdf") {
found = true found = true
break break
@@ -161,8 +164,9 @@ func TestStealthChromiumScripts_MimeTypes(t *testing.T) {
} }
func TestStealthChromiumScripts_WindowChrome(t *testing.T) { func TestStealthChromiumScripts_WindowChrome(t *testing.T) {
scripts := buildChromiumStealthScripts(chromiumHWProfiles[0])
found := false found := false
for _, s := range stealthChromiumScripts { for _, s := range scripts {
if strings.Contains(s, "window.chrome") && strings.Contains(s, "runtime") { if strings.Contains(s, "window.chrome") && strings.Contains(s, "runtime") {
found = true found = true
break break
@@ -174,8 +178,9 @@ func TestStealthChromiumScripts_WindowChrome(t *testing.T) {
} }
func TestStealthChromiumScripts_ChromeApp(t *testing.T) { func TestStealthChromiumScripts_ChromeApp(t *testing.T) {
scripts := buildChromiumStealthScripts(chromiumHWProfiles[0])
found := false 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") { if strings.Contains(s, "chrome.app") && strings.Contains(s, "chrome.csi") && strings.Contains(s, "chrome.loadTimes") {
found = true found = true
break break
@@ -187,8 +192,9 @@ func TestStealthChromiumScripts_ChromeApp(t *testing.T) {
} }
func TestStealthChromiumScripts_WebGLSpoof(t *testing.T) { func TestStealthChromiumScripts_WebGLSpoof(t *testing.T) {
scripts := buildChromiumStealthScripts(chromiumHWProfiles[0])
found := false found := false
for _, s := range stealthChromiumScripts { for _, s := range scripts {
if strings.Contains(s, "37446") && strings.Contains(s, "ANGLE") { if strings.Contains(s, "37446") && strings.Contains(s, "ANGLE") {
found = true found = true
break break
@@ -200,8 +206,9 @@ func TestStealthChromiumScripts_WebGLSpoof(t *testing.T) {
} }
func TestStealthChromiumScripts_NavigatorConnection(t *testing.T) { func TestStealthChromiumScripts_NavigatorConnection(t *testing.T) {
scripts := buildChromiumStealthScripts(chromiumHWProfiles[0])
found := false found := false
for _, s := range stealthChromiumScripts { for _, s := range scripts {
if strings.Contains(s, "connection") && strings.Contains(s, "effectiveType") { if strings.Contains(s, "connection") && strings.Contains(s, "effectiveType") {
found = true found = true
break break
@@ -213,8 +220,9 @@ func TestStealthChromiumScripts_NavigatorConnection(t *testing.T) {
} }
func TestStealthChromiumScripts_CDPCleanup(t *testing.T) { func TestStealthChromiumScripts_CDPCleanup(t *testing.T) {
scripts := buildChromiumStealthScripts(chromiumHWProfiles[0])
found := false found := false
for _, s := range stealthChromiumScripts { for _, s := range scripts {
if strings.Contains(s, "cdc_") && strings.Contains(s, "delete") { if strings.Contains(s, "cdc_") && strings.Contains(s, "delete") {
found = true found = true
break break
@@ -226,8 +234,9 @@ func TestStealthChromiumScripts_CDPCleanup(t *testing.T) {
} }
func TestStealthChromiumScripts_UserAgentStrip(t *testing.T) { func TestStealthChromiumScripts_UserAgentStrip(t *testing.T) {
scripts := buildChromiumStealthScripts(chromiumHWProfiles[0])
found := false found := false
for _, s := range stealthChromiumScripts { for _, s := range scripts {
if strings.Contains(s, "HeadlessChrome") && strings.Contains(s, "userAgent") { if strings.Contains(s, "HeadlessChrome") && strings.Contains(s, "userAgent") {
found = true found = true
break break
@@ -241,14 +250,16 @@ func TestStealthChromiumScripts_UserAgentStrip(t *testing.T) {
// --- Firefox scripts --- // --- Firefox scripts ---
func TestStealthFirefoxScripts_Count(t *testing.T) { func TestStealthFirefoxScripts_Count(t *testing.T) {
if len(stealthFirefoxScripts) != 5 { scripts := buildFirefoxStealthScripts(firefoxHWProfiles[0])
t.Fatalf("expected 5 firefox stealth scripts, got %d", len(stealthFirefoxScripts)) if len(scripts) != 5 {
t.Fatalf("expected 5 firefox stealth scripts, got %d", len(scripts))
} }
} }
func TestStealthFirefoxScripts_WebdriverHardening(t *testing.T) { func TestStealthFirefoxScripts_WebdriverHardening(t *testing.T) {
scripts := buildFirefoxStealthScripts(firefoxHWProfiles[0])
found := false found := false
for _, s := range stealthFirefoxScripts { for _, s := range scripts {
if strings.Contains(s, "getOwnPropertyDescriptor") && strings.Contains(s, "webdriver") { if strings.Contains(s, "getOwnPropertyDescriptor") && strings.Contains(s, "webdriver") {
found = true found = true
break break
@@ -260,8 +271,9 @@ func TestStealthFirefoxScripts_WebdriverHardening(t *testing.T) {
} }
func TestStealthFirefoxScripts_WebGLSpoof(t *testing.T) { func TestStealthFirefoxScripts_WebGLSpoof(t *testing.T) {
scripts := buildFirefoxStealthScripts(firefoxHWProfiles[0])
found := false found := false
for _, s := range stealthFirefoxScripts { for _, s := range scripts {
if strings.Contains(s, "37446") && strings.Contains(s, "Mesa DRI") { if strings.Contains(s, "37446") && strings.Contains(s, "Mesa DRI") {
found = true found = true
break break
@@ -273,8 +285,9 @@ func TestStealthFirefoxScripts_WebGLSpoof(t *testing.T) {
} }
func TestStealthFirefoxScripts_MozInnerScreen(t *testing.T) { func TestStealthFirefoxScripts_MozInnerScreen(t *testing.T) {
scripts := buildFirefoxStealthScripts(firefoxHWProfiles[0])
found := false found := false
for _, s := range stealthFirefoxScripts { for _, s := range scripts {
if strings.Contains(s, "mozInnerScreenX") && strings.Contains(s, "mozInnerScreenY") { if strings.Contains(s, "mozInnerScreenX") && strings.Contains(s, "mozInnerScreenY") {
found = true found = true
break break
@@ -286,8 +299,9 @@ func TestStealthFirefoxScripts_MozInnerScreen(t *testing.T) {
} }
func TestStealthFirefoxScripts_HardwareConcurrency(t *testing.T) { func TestStealthFirefoxScripts_HardwareConcurrency(t *testing.T) {
scripts := buildFirefoxStealthScripts(firefoxHWProfiles[0])
found := false found := false
for _, s := range stealthFirefoxScripts { for _, s := range scripts {
if strings.Contains(s, "hardwareConcurrency") { if strings.Contains(s, "hardwareConcurrency") {
found = true found = true
break break
@@ -299,8 +313,9 @@ func TestStealthFirefoxScripts_HardwareConcurrency(t *testing.T) {
} }
func TestStealthFirefoxScripts_PDFjsPlugins(t *testing.T) { func TestStealthFirefoxScripts_PDFjsPlugins(t *testing.T) {
scripts := buildFirefoxStealthScripts(firefoxHWProfiles[0])
found := false found := false
for _, s := range stealthFirefoxScripts { for _, s := range scripts {
if strings.Contains(s, "PDF.js") && strings.Contains(s, "plugins") { if strings.Contains(s, "PDF.js") && strings.Contains(s, "plugins") {
found = true found = true
break break
@@ -314,17 +329,19 @@ func TestStealthFirefoxScripts_PDFjsPlugins(t *testing.T) {
// --- Cross-category validation --- // --- Cross-category validation ---
func TestStealthScripts_NoOverlap(t *testing.T) { func TestStealthScripts_NoOverlap(t *testing.T) {
chromiumScripts := buildChromiumStealthScripts(chromiumHWProfiles[0])
firefoxScripts := buildFirefoxStealthScripts(firefoxHWProfiles[0])
all := make(map[string]string) // script -> category all := make(map[string]string) // script -> category
for _, s := range stealthCommonScripts { for _, s := range stealthCommonScripts {
all[s] = "common" all[s] = "common"
} }
for _, s := range stealthChromiumScripts { for _, s := range chromiumScripts {
if cat, ok := all[s]; ok { if cat, ok := all[s]; ok {
t.Fatalf("chromium script also appears in %s category", cat) t.Fatalf("chromium script also appears in %s category", cat)
} }
all[s] = "chromium" all[s] = "chromium"
} }
for _, s := range stealthFirefoxScripts { for _, s := range firefoxScripts {
if cat, ok := all[s]; ok { if cat, ok := all[s]; ok {
t.Fatalf("firefox script also appears in %s category", cat) 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) { func TestStealthChromiumScripts_NoFirefoxMarkers(t *testing.T) {
scripts := buildChromiumStealthScripts(chromiumHWProfiles[0])
firefoxMarkers := []string{"mozInnerScreen", "Mesa DRI", "PDF.js"} firefoxMarkers := []string{"mozInnerScreen", "Mesa DRI", "PDF.js"}
for _, s := range stealthChromiumScripts { for _, s := range scripts {
for _, marker := range firefoxMarkers { for _, marker := range firefoxMarkers {
if strings.Contains(s, marker) { if strings.Contains(s, marker) {
t.Fatalf("chromium script contains Firefox-specific marker %q", 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) { 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"} 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 { for _, marker := range chromiumMarkers {
if strings.Contains(s, marker) { if strings.Contains(s, marker) {
t.Fatalf("firefox script contains Chromium-specific marker %q", 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) 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)
}
}
}