Initial commit: navegator - Chrome CDP automation for LLMs
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>
This commit is contained in:
@@ -0,0 +1,467 @@
|
||||
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:])
|
||||
}
|
||||
Reference in New Issue
Block a user