mirror of
https://github.com/lukaszraczylo/mcp-captcharesolver.git
synced 2026-10-16 01:07:54 +00:00
feat(stealth): make evasion script coherent with generated fingerprint
- Convert assets/evasions.js to evasions.js.tmpl (Go text/template) - Placeholders: WebGLVendor, WebGLRenderer, Platform, Languages - Add navigator.platform spoof from fingerprint - ScriptParams gains OS, Browser, Locale, Seed fields (fieldalignment ordered) - ScriptResult gains Fingerprint field — returns the identity the script was rendered for - StealthScript calls GenerateFingerprint and renders template; single call gives both script and coherent fingerprint - languagesJS: locale to JS list (en-GB -> 'en-GB', 'en') - Template parsed once at init via template.Must - StealthScriptIn in tools.go gains OS, Browser, Locale, Seed pass-through - Tests: added TestStealthScriptCoherence asserting rendered values match fingerprint
This commit is contained in:
+76
-13
@@ -1,13 +1,27 @@
|
||||
package stealth
|
||||
|
||||
import _ "embed"
|
||||
import (
|
||||
_ "embed"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
//go:embed assets/evasions.js
|
||||
var evasionsJS string
|
||||
//go:embed assets/evasions.js.tmpl
|
||||
var evasionsTmplSrc string
|
||||
|
||||
// ScriptParams selects the target engine for apply instructions.
|
||||
// evasionTmpl is parsed once at startup; template.Must panics on bad syntax.
|
||||
var evasionTmpl = template.Must(template.New("evasions").Parse(evasionsTmplSrc))
|
||||
|
||||
// ScriptParams selects identity and target engine for apply instructions.
|
||||
// Fields ordered for fieldalignment (largest to smallest, then strings).
|
||||
type ScriptParams struct {
|
||||
Engine string // playwright | cdp | generic
|
||||
Seed uint64 // deterministic generation; 0 → random
|
||||
OS string // windows | macos | linux
|
||||
Browser string // chrome
|
||||
Locale string // e.g. en-US, en-GB
|
||||
Engine string // playwright | cdp | generic
|
||||
}
|
||||
|
||||
// ApplyInfo tells the caller how to inject the script.
|
||||
@@ -18,13 +32,59 @@ type ApplyInfo struct {
|
||||
|
||||
// ScriptResult is the injectable evasion bundle plus metadata.
|
||||
type ScriptResult struct {
|
||||
Script string `json:"script"`
|
||||
Apply ApplyInfo `json:"apply"`
|
||||
IncludedEvasions []string `json:"included_evasions"`
|
||||
Script string `json:"script"`
|
||||
Apply ApplyInfo `json:"apply"`
|
||||
Fingerprint Fingerprint `json:"fingerprint"`
|
||||
IncludedEvasions []string `json:"included_evasions"`
|
||||
}
|
||||
|
||||
// StealthScript returns the evasion init script and engine-specific apply hints.
|
||||
// evasionData is the template data bag.
|
||||
type evasionData struct {
|
||||
WebGLVendor string
|
||||
WebGLRenderer string
|
||||
Platform string
|
||||
Languages string // pre-rendered JS list, e.g. 'en-GB', 'en'
|
||||
}
|
||||
|
||||
// languagesJS converts a locale string to a JS list of quoted language tags.
|
||||
// "en-GB" → `'en-GB', 'en'`; "en" → `'en'`; "" → `'en-US', 'en'`
|
||||
func languagesJS(locale string) string {
|
||||
if locale == "" {
|
||||
locale = "en-US"
|
||||
}
|
||||
parts := strings.SplitN(locale, "-", 2)
|
||||
if len(parts) == 2 && parts[0] != locale {
|
||||
return fmt.Sprintf("'%s', '%s'", locale, parts[0])
|
||||
}
|
||||
return fmt.Sprintf("'%s'", locale)
|
||||
}
|
||||
|
||||
// StealthScript returns an evasion init script rendered from a coherent
|
||||
// Fingerprint, plus engine-specific apply hints. The Fingerprint used for
|
||||
// rendering is included in ScriptResult so callers can apply it without a
|
||||
// second call. Empty params produce valid output (windows/chrome/en-US defaults).
|
||||
func StealthScript(p ScriptParams) ScriptResult {
|
||||
fp := GenerateFingerprint(FingerprintParams{
|
||||
OS: p.OS,
|
||||
Browser: p.Browser,
|
||||
Locale: p.Locale,
|
||||
Seed: p.Seed,
|
||||
})
|
||||
|
||||
var buf bytes.Buffer
|
||||
// Execute errors are non-fatal — the template is static aside from the data
|
||||
// substitution; if it somehow fails, fall back to a descriptive comment.
|
||||
if err := evasionTmpl.Execute(&buf, evasionData{
|
||||
WebGLVendor: fp.WebGLVendor,
|
||||
WebGLRenderer: fp.WebGLRenderer,
|
||||
Platform: fp.Platform,
|
||||
Languages: languagesJS(fp.Locale),
|
||||
}); err != nil {
|
||||
// Fallback: return an empty-IIFE comment so the script is valid JS.
|
||||
buf.Reset()
|
||||
fmt.Fprintf(&buf, "(() => { /* evasion render error: %v */ })();", err)
|
||||
}
|
||||
|
||||
apply := ApplyInfo{How: "Inject before any page script runs.", API: "page.addInitScript(script)"}
|
||||
switch p.Engine {
|
||||
case "cdp":
|
||||
@@ -32,12 +92,15 @@ func StealthScript(p ScriptParams) ScriptResult {
|
||||
case "generic", "":
|
||||
apply.API = "evaluate the script on every new document before navigation"
|
||||
}
|
||||
|
||||
return ScriptResult{
|
||||
Script: evasionsJS,
|
||||
Apply: apply,
|
||||
Script: buf.String(),
|
||||
Apply: apply,
|
||||
Fingerprint: fp,
|
||||
IncludedEvasions: []string{
|
||||
"navigator.webdriver", "navigator.languages", "navigator.plugins",
|
||||
"chrome.runtime", "permissions.query", "WebGL vendor/renderer",
|
||||
"navigator.webdriver", "navigator.languages", "navigator.platform",
|
||||
"navigator.plugins", "chrome.runtime", "permissions.query",
|
||||
"WebGL vendor/renderer",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user