3253828fef
Add complete navegator system for stealthy browser automation: - CDP client with WebSocket communication - Browser API with navigation, storage, network, runtime - Stealth flags and anti-detection scripts - Persistent profile support - Examples and comprehensive documentation Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
468 lines
13 KiB
Go
468 lines
13 KiB
Go
package stealth
|
|
|
|
// StealthFlags contiene todas las flags de Chrome para evasión de detección.
|
|
// Ver docs/STEALTH_FLAGS.md para documentación completa.
|
|
type StealthFlags struct {
|
|
// UserDataDir es la ruta al perfil persistente de Chrome
|
|
UserDataDir string
|
|
|
|
// ProfileName es el nombre del perfil dentro de UserDataDir
|
|
ProfileName string
|
|
|
|
// WindowSize define el tamaño de la ventana (ancho,alto)
|
|
WindowSize [2]int
|
|
|
|
// UserAgent personalizado (vacío = usar default de Chrome)
|
|
UserAgent string
|
|
|
|
// Headless activa modo headless si es true
|
|
Headless bool
|
|
|
|
// NoSandbox desactiva sandbox (PELIGROSO - solo Docker/VMs)
|
|
NoSandbox bool
|
|
|
|
// DisableWebSecurity desactiva CORS (solo testing)
|
|
DisableWebSecurity bool
|
|
|
|
// EnableLogging activa logs de Chrome
|
|
EnableLogging bool
|
|
|
|
// LogLevel define nivel de logs (0=INFO, 1=WARNING, 2=ERROR)
|
|
LogLevel int
|
|
|
|
// RemoteDebuggingPort puerto para CDP (0 = aleatorio)
|
|
RemoteDebuggingPort int
|
|
|
|
// CustomFlags flags adicionales personalizadas
|
|
CustomFlags []string
|
|
}
|
|
|
|
// DefaultStealthFlags retorna configuración stealth por defecto.
|
|
func DefaultStealthFlags() *StealthFlags {
|
|
return &StealthFlags{
|
|
ProfileName: "Default",
|
|
WindowSize: [2]int{1920, 1080},
|
|
Headless: true,
|
|
NoSandbox: false,
|
|
DisableWebSecurity: false,
|
|
EnableLogging: false,
|
|
LogLevel: 2, // ERROR
|
|
RemoteDebuggingPort: 0, // aleatorio
|
|
}
|
|
}
|
|
|
|
// Build convierte StealthFlags a slice de argumentos para Chrome.
|
|
func (sf *StealthFlags) Build() []string {
|
|
flags := []string{
|
|
// ============================================
|
|
// CRÍTICAS - SIEMPRE ACTIVADAS
|
|
// ============================================
|
|
|
|
// Elimina navigator.webdriver = true
|
|
"--disable-blink-features=AutomationControlled",
|
|
|
|
// Evita flag --enable-automation
|
|
"--exclude-switches=enable-automation",
|
|
|
|
// ============================================
|
|
// CONFIGURACIÓN DE PERFIL
|
|
// ============================================
|
|
}
|
|
|
|
// User data dir (perfil persistente)
|
|
if sf.UserDataDir != "" {
|
|
flags = append(flags, "--user-data-dir="+sf.UserDataDir)
|
|
if sf.ProfileName != "" {
|
|
flags = append(flags, "--profile-directory="+sf.ProfileName)
|
|
}
|
|
}
|
|
|
|
// ============================================
|
|
// HEADLESS Y GPU
|
|
// ============================================
|
|
|
|
if sf.Headless {
|
|
// Nuevo modo headless estable
|
|
flags = append(flags, "--headless=new")
|
|
// Desactivar GPU en headless
|
|
flags = append(flags, "--disable-gpu")
|
|
// Ocultar scrollbars
|
|
flags = append(flags, "--hide-scrollbars")
|
|
// Silenciar audio
|
|
flags = append(flags, "--mute-audio")
|
|
}
|
|
|
|
// ============================================
|
|
// CONFIGURACIÓN DE VENTANA
|
|
// ============================================
|
|
|
|
if sf.WindowSize[0] > 0 && sf.WindowSize[1] > 0 {
|
|
flags = append(flags,
|
|
"--window-size="+intToString(sf.WindowSize[0])+","+intToString(sf.WindowSize[1]),
|
|
)
|
|
}
|
|
|
|
if !sf.Headless {
|
|
flags = append(flags, "--start-maximized")
|
|
}
|
|
|
|
// ============================================
|
|
// DESACTIVAR PROMPTS Y POPUPS DE CHROME
|
|
// ============================================
|
|
|
|
// No mostrar "¿Hacer Chrome tu navegador predeterminado?"
|
|
flags = append(flags, "--no-default-browser-check")
|
|
|
|
// No mostrar prompt de "restaurar sesión"
|
|
flags = append(flags, "--disable-session-crashed-bubble")
|
|
|
|
// No mostrar infobars (barras de información)
|
|
flags = append(flags, "--disable-infobars")
|
|
|
|
// No mostrar "Chrome está siendo controlado por software automatizado"
|
|
// (ya cubierto por --exclude-switches=enable-automation)
|
|
|
|
// Desactivar prompts de guardar contraseñas
|
|
flags = append(flags, "--disable-save-password-bubble")
|
|
|
|
// No mostrar primera experiencia de usuario
|
|
flags = append(flags, "--no-first-run")
|
|
|
|
// Desactivar componentes de sync
|
|
flags = append(flags, "--disable-sync")
|
|
|
|
// Desactivar ofertas de instalación de Chrome
|
|
flags = append(flags, "--disable-component-update")
|
|
|
|
// ============================================
|
|
// USER AGENT
|
|
// ============================================
|
|
|
|
if sf.UserAgent != "" {
|
|
flags = append(flags, "--user-agent="+sf.UserAgent)
|
|
}
|
|
|
|
// ============================================
|
|
// OPTIMIZACIÓN Y ESTABILIDAD
|
|
// ============================================
|
|
|
|
// Evita problemas en Docker/containers
|
|
flags = append(flags, "--disable-dev-shm-usage")
|
|
|
|
// Reduce superficie de detección
|
|
flags = append(flags, "--disable-extensions")
|
|
|
|
// Mejora rendimiento
|
|
flags = append(flags, "--disable-plugins")
|
|
|
|
// Evita throttling de timers
|
|
flags = append(flags, "--disable-background-timer-throttling")
|
|
|
|
// Mantiene ventanas ocultas activas
|
|
flags = append(flags, "--disable-backgrounding-occluded-windows")
|
|
|
|
// Mantiene renderer activo
|
|
flags = append(flags, "--disable-renderer-backgrounding")
|
|
|
|
// Permite muchos comandos CDP rápidos
|
|
flags = append(flags, "--disable-ipc-flooding-protection")
|
|
|
|
// ============================================
|
|
// PRIVACIDAD Y UI
|
|
// ============================================
|
|
|
|
// Bloquea notificaciones
|
|
flags = append(flags, "--disable-notifications")
|
|
|
|
// Permite popups
|
|
flags = append(flags, "--disable-popup-blocking")
|
|
|
|
// Desactiva UI de traducción
|
|
flags = append(flags, "--disable-features=TranslateUI")
|
|
|
|
// Desactiva Privacy Sandbox
|
|
flags = append(flags, "--disable-features=PrivacySandboxSettings4")
|
|
|
|
// ============================================
|
|
// FLAGS OPCIONALES (PELIGROSAS/DEBUG)
|
|
// ============================================
|
|
|
|
// SANDBOX - Solo Docker/VMs confiables
|
|
if sf.NoSandbox {
|
|
flags = append(flags, "--no-sandbox", "--disable-setuid-sandbox")
|
|
}
|
|
|
|
// WEB SECURITY - Solo testing
|
|
if sf.DisableWebSecurity {
|
|
flags = append(flags, "--disable-web-security")
|
|
flags = append(flags, "--disable-features=IsolateOrigins,site-per-process")
|
|
flags = append(flags, "--disable-site-isolation-trials")
|
|
}
|
|
|
|
// LOGGING - Solo debugging
|
|
if sf.EnableLogging {
|
|
flags = append(flags, "--enable-logging")
|
|
flags = append(flags, "--v=1")
|
|
flags = append(flags, "--log-level="+intToString(sf.LogLevel))
|
|
}
|
|
|
|
// ============================================
|
|
// REMOTE DEBUGGING (CDP)
|
|
// ============================================
|
|
|
|
if sf.RemoteDebuggingPort > 0 {
|
|
flags = append(flags, "--remote-debugging-port="+intToString(sf.RemoteDebuggingPort))
|
|
} else {
|
|
// Puerto aleatorio, CDP asignará uno
|
|
flags = append(flags, "--remote-debugging-port=0")
|
|
}
|
|
|
|
// Escuchar en todas las interfaces
|
|
flags = append(flags, "--remote-debugging-address=0.0.0.0")
|
|
|
|
// ============================================
|
|
// CUSTOM FLAGS
|
|
// ============================================
|
|
|
|
if len(sf.CustomFlags) > 0 {
|
|
flags = append(flags, sf.CustomFlags...)
|
|
}
|
|
|
|
return flags
|
|
}
|
|
|
|
// GetAntiDetectionScript retorna el código JavaScript para inyectar
|
|
// en cada página y sobrescribir propiedades que delatan automatización.
|
|
func GetAntiDetectionScript() string {
|
|
return `
|
|
// ============================================
|
|
// ANTI-DETECTION SCRIPT
|
|
// ============================================
|
|
|
|
// Sobrescribir navigator.webdriver
|
|
Object.defineProperty(navigator, 'webdriver', {
|
|
get: () => undefined
|
|
});
|
|
|
|
// Eliminar propiedades de Selenium/WebDriver
|
|
delete window.navigator.__proto__.webdriver;
|
|
delete window.navigator.webdriver;
|
|
delete window._selenium;
|
|
delete window._webdriver;
|
|
delete window.callSelenium;
|
|
delete window.callPhantom;
|
|
delete window._phantom;
|
|
|
|
// Mock chrome runtime
|
|
window.chrome = {
|
|
app: {
|
|
isInstalled: false,
|
|
InstallState: {
|
|
DISABLED: 'disabled',
|
|
INSTALLED: 'installed',
|
|
NOT_INSTALLED: 'not_installed'
|
|
},
|
|
RunningState: {
|
|
CANNOT_RUN: 'cannot_run',
|
|
READY_TO_RUN: 'ready_to_run',
|
|
RUNNING: 'running'
|
|
}
|
|
},
|
|
runtime: {
|
|
OnInstalledReason: {
|
|
CHROME_UPDATE: 'chrome_update',
|
|
INSTALL: 'install',
|
|
SHARED_MODULE_UPDATE: 'shared_module_update',
|
|
UPDATE: 'update'
|
|
},
|
|
OnRestartRequiredReason: {
|
|
APP_UPDATE: 'app_update',
|
|
OS_UPDATE: 'os_update',
|
|
PERIODIC: 'periodic'
|
|
},
|
|
PlatformArch: {
|
|
ARM: 'arm',
|
|
ARM64: 'arm64',
|
|
MIPS: 'mips',
|
|
MIPS64: 'mips64',
|
|
X86_32: 'x86-32',
|
|
X86_64: 'x86-64'
|
|
},
|
|
PlatformNaclArch: {
|
|
ARM: 'arm',
|
|
MIPS: 'mips',
|
|
MIPS64: 'mips64',
|
|
X86_32: 'x86-32',
|
|
X86_64: 'x86-64'
|
|
},
|
|
PlatformOs: {
|
|
ANDROID: 'android',
|
|
CROS: 'cros',
|
|
LINUX: 'linux',
|
|
MAC: 'mac',
|
|
OPENBSD: 'openbsd',
|
|
WIN: 'win'
|
|
},
|
|
RequestUpdateCheckStatus: {
|
|
NO_UPDATE: 'no_update',
|
|
THROTTLED: 'throttled',
|
|
UPDATE_AVAILABLE: 'update_available'
|
|
}
|
|
},
|
|
csi: function() {},
|
|
loadTimes: function() {}
|
|
};
|
|
|
|
// Mock permissions.query
|
|
const originalQuery = window.navigator.permissions.query;
|
|
window.navigator.permissions.query = (parameters) => (
|
|
parameters.name === 'notifications' ?
|
|
Promise.resolve({ state: Notification.permission }) :
|
|
originalQuery(parameters)
|
|
);
|
|
|
|
// Plugins array mock (navegadores reales tienen plugins)
|
|
Object.defineProperty(navigator, 'plugins', {
|
|
get: () => [
|
|
{
|
|
0: {type: "application/x-google-chrome-pdf", suffixes: "pdf", description: "Portable Document Format", enabledPlugin: Plugin},
|
|
description: "Portable Document Format",
|
|
filename: "internal-pdf-viewer",
|
|
length: 1,
|
|
name: "Chrome PDF Plugin"
|
|
},
|
|
{
|
|
0: {type: "application/pdf", suffixes: "pdf", description: "", enabledPlugin: Plugin},
|
|
description: "",
|
|
filename: "mhjfbmdgcfjbbpaeojofohoefgiehjai",
|
|
length: 1,
|
|
name: "Chrome PDF Viewer"
|
|
},
|
|
{
|
|
0: {type: "application/x-nacl", suffixes: "", description: "Native Client Executable", enabledPlugin: Plugin},
|
|
1: {type: "application/x-pnacl", suffixes: "", description: "Portable Native Client Executable", enabledPlugin: Plugin},
|
|
description: "",
|
|
filename: "internal-nacl-plugin",
|
|
length: 2,
|
|
name: "Native Client"
|
|
}
|
|
]
|
|
});
|
|
|
|
// Languages array (debe tener contenido realista)
|
|
Object.defineProperty(navigator, 'languages', {
|
|
get: () => ['en-US', 'en', 'es']
|
|
});
|
|
|
|
// Platform fix
|
|
Object.defineProperty(navigator, 'platform', {
|
|
get: () => 'Win32'
|
|
});
|
|
|
|
// Vendor fix
|
|
Object.defineProperty(navigator, 'vendor', {
|
|
get: () => 'Google Inc.'
|
|
});
|
|
|
|
// Connection mock
|
|
Object.defineProperty(navigator, 'connection', {
|
|
get: () => ({
|
|
effectiveType: '4g',
|
|
rtt: 100,
|
|
downlink: 10,
|
|
saveData: false
|
|
})
|
|
});
|
|
|
|
// Hardware concurrency (núcleos de CPU)
|
|
Object.defineProperty(navigator, 'hardwareConcurrency', {
|
|
get: () => 8
|
|
});
|
|
|
|
// Device memory (GB RAM)
|
|
Object.defineProperty(navigator, 'deviceMemory', {
|
|
get: () => 8
|
|
});
|
|
|
|
// Battery mock (solo si la API existe)
|
|
if (navigator.getBattery) {
|
|
const originalGetBattery = navigator.getBattery;
|
|
navigator.getBattery = () => originalGetBattery().then(battery => {
|
|
Object.defineProperty(battery, 'charging', { get: () => true });
|
|
Object.defineProperty(battery, 'chargingTime', { get: () => 0 });
|
|
Object.defineProperty(battery, 'dischargingTime', { get: () => Infinity });
|
|
Object.defineProperty(battery, 'level', { get: () => 1 });
|
|
return battery;
|
|
});
|
|
}
|
|
|
|
// Console debug signature removal
|
|
const originalConsole = {
|
|
log: console.log,
|
|
warn: console.warn,
|
|
error: console.error,
|
|
debug: console.debug
|
|
};
|
|
|
|
console.log = (...args) => originalConsole.log.apply(console, args);
|
|
console.warn = (...args) => originalConsole.warn.apply(console, args);
|
|
console.error = (...args) => originalConsole.error.apply(console, args);
|
|
console.debug = (...args) => originalConsole.debug.apply(console, args);
|
|
|
|
// Performance timing fix
|
|
if (window.performance && window.performance.timing) {
|
|
Object.defineProperty(window.performance.timing, 'navigationStart', {
|
|
get: () => Date.now() - Math.floor(Math.random() * 10000)
|
|
});
|
|
}
|
|
|
|
// Screen dimensions mock (deben coincidir con window size)
|
|
Object.defineProperty(window.screen, 'width', {
|
|
get: () => 1920
|
|
});
|
|
|
|
Object.defineProperty(window.screen, 'height', {
|
|
get: () => 1080
|
|
});
|
|
|
|
Object.defineProperty(window.screen, 'availWidth', {
|
|
get: () => 1920
|
|
});
|
|
|
|
Object.defineProperty(window.screen, 'availHeight', {
|
|
get: () => 1040 // Menos barra de tareas
|
|
});
|
|
|
|
// ============================================
|
|
// FIN ANTI-DETECTION SCRIPT
|
|
// ============================================
|
|
`
|
|
}
|
|
|
|
// intToString convierte int a string (helper simple)
|
|
func intToString(n int) string {
|
|
if n == 0 {
|
|
return "0"
|
|
}
|
|
|
|
var buf [20]byte
|
|
i := len(buf) - 1
|
|
neg := n < 0
|
|
if neg {
|
|
n = -n
|
|
}
|
|
|
|
for n > 0 {
|
|
buf[i] = byte('0' + n%10)
|
|
n /= 10
|
|
i--
|
|
}
|
|
|
|
if neg {
|
|
buf[i] = '-'
|
|
i--
|
|
}
|
|
|
|
return string(buf[i+1:])
|
|
}
|