use browser_oxide::stealth::StealthProfile;
use browser_oxide::Page;
#[derive(Debug, PartialEq)]
enum Verdict {
Pass,
Intr,
Block,
Error,
}
#[allow(
dead_code,
reason = "diagnostic capture struct; not all fields are asserted"
)]
struct BlockerProbeResult {
name: String,
url: String,
protection: String,
baseline_status: u16,
baseline_size: usize,
baseline_verdict: Verdict,
solver_status: u16,
solver_size: usize,
solver_verdict: Verdict,
}
impl BlockerProbeResult {
fn print(&self) {
let baseline_str = format!("{:?} ({}b)", self.baseline_verdict, self.baseline_size);
let solver_str = format!("{:?} ({}b)", self.solver_verdict, self.solver_size);
let status = if self.solver_verdict == Verdict::Pass {
"[WIN ]"
} else {
"[FAIL ]"
};
println!(
"{} baseline={:<12} solver={:<12} {:<14} {} — {}",
status, baseline_str, solver_str, self.protection, self.name, self.url
);
}
}
fn classify(
body: &str,
status: u16,
positive: &[&str],
negative: &[&str],
min_size: usize,
) -> Verdict {
if status >= 400 && status != 403 && status != 429 {
return Verdict::Error;
}
if body.len() >= min_size {
return Verdict::Pass;
}
let small_body = body.len() < min_size.min(10_000);
let has_negative = negative.iter().any(|m| body.contains(m));
if small_body && has_negative {
for marker in negative {
if body.contains(marker)
&& (marker.contains("Reference Error")
|| marker.contains("WAFfailover")
|| marker.contains("Access Denied"))
{
return Verdict::Block;
}
}
return Verdict::Intr;
}
if positive.iter().any(|m| body.contains(m)) {
return Verdict::Pass;
}
if body.is_empty() {
return Verdict::Error;
}
Verdict::Intr
}
async fn probe_site(
name: &str,
url: &str,
protection: &str,
profile: StealthProfile,
positive: &[&str],
negative: &[&str],
min_size: usize,
) -> BlockerProbeResult {
let client = browser_oxide::net::HttpClient::new(&profile).unwrap();
let (baseline_status, baseline_size, baseline_verdict) = match client.get_follow(url, 10).await
{
Ok(resp) => {
let body = resp.text();
let size = body.len();
let v = classify(&body, resp.status, positive, negative, min_size);
(resp.status, size, v)
}
Err(_) => (0, 0, Verdict::Error),
};
let (solver_status, solver_size, solver_verdict) = match Page::navigate(url, profile, 5).await {
Ok(mut page) => {
let body = page.content();
let size = body.len();
let v = classify(&body, 200, positive, negative, min_size);
(200, size, v)
}
Err(_) => (0, 0, Verdict::Error),
};
BlockerProbeResult {
name: name.to_string(),
url: url.to_string(),
protection: protection.to_string(),
baseline_status,
baseline_size,
baseline_verdict,
solver_status,
solver_size,
solver_verdict,
}
}
#[tokio::test]
#[ignore]
async fn tier05_blockers_all() {
let mut results: Vec<BlockerProbeResult> = Vec::new();
results.push(
probe_site(
"adidas",
"https://www.adidas.com/us",
"akamai-bmp-v3",
browser_oxide::stealth::chrome_148_macos(),
&[
"adidas-us",
"product-card",
"utag_data",
"Sneakers and Activewear",
],
&[
"sec-if-cpt-container",
"Pardon Our Interruption",
"Reference Error",
"WAFfailover",
],
50_000,
)
.await,
);
results.push(
probe_site(
"homedepot",
"https://www.homedepot.com/",
"akamai-bmp-v3",
browser_oxide::stealth::chrome_148_windows(),
&["homedepot", "product", "Home Depot"],
&[
"sec-if-cpt-container",
"Pardon Our Interruption",
"Reference Error",
"Access Denied",
],
50_000,
)
.await,
);
results.push(
probe_site(
"canadagoose",
"https://www.canadagoose.com/us/en/home-page",
"kasada",
browser_oxide::stealth::chrome_148_windows(),
&["Canada Goose", "product", "shop"],
&["x-kpsdk", "KPSDK", "403", "ips.js"],
50_000,
)
.await,
);
results.push(
probe_site(
"hyatt",
"https://www.hyatt.com/",
"kasada",
browser_oxide::stealth::chrome_148_windows(),
&["Hyatt", "hotel", "book"],
&["x-kpsdk", "KPSDK", "Access denied"],
50_000,
)
.await,
);
results.push(
probe_site(
"wildberries",
"https://www.wildberries.ru/",
"wbaas",
browser_oxide::stealth::presets::chrome_148_ru(),
&["wildberries", "Wildberries", "товар"],
&["challenge_fingerprint", "x-wbaas-token", "QRATOR"],
80_000,
)
.await,
);
results.push(
probe_site(
"dns_shop",
"https://www.dns-shop.ru/",
"qrator",
browser_oxide::stealth::presets::chrome_148_ru(),
&["dns-shop", "DNS", "каталог"],
&["QRATOR", "Rate limit", "blocked"],
80_000,
)
.await,
);
results.push(
probe_site(
"ozon",
"https://www.ozon.ru/",
"ddos-guard",
browser_oxide::stealth::presets::chrome_148_ru(),
&["ozon", "Ozon", "товар"],
&["ddos-guard", "challenge", "cf-chl"],
80_000,
)
.await,
);
results.push(
probe_site(
"yandex",
"https://ya.ru/",
"smartcaptcha",
browser_oxide::stealth::presets::chrome_148_ru(),
&["data-bem", "yandex-verification", "homer"],
&["SmartCaptcha", "smart-captcha", "\"captcha\""],
30_000,
)
.await,
);
println!("\n=== Tier 0.5 Blocker Re-Probe Results ===\n");
let total = results.len();
let mut wins = 0usize;
let mut base_only = 0usize;
let mut fails = 0usize;
for r in &results {
r.print();
match r.solver_verdict {
Verdict::Pass => wins += 1,
_ if r.baseline_verdict == Verdict::Pass => base_only += 1,
_ => fails += 1,
}
}
println!();
println!("Summary: {wins}/{total} solver-PASS, {base_only} baseline-only PASS, {fails} FAIL");
}