CloakBrowser 深度解析从 Playwright 包装层到源码级指纹 Chromium,讲透架构、Humanize、GeoIP、供应链安全与生产实战

一、先给结论:CloakBrowser 到底值不值得研究?

项目地址:https://github.com/CloakHQ/CloakBrowser

本文基于 2026 年 8 月初的 main 分支与 v0.5.3 代码结构整理。CloakBrowser 更新频繁,具体二进制版本、授权方案和平台能力应以项目最新说明与 cloakbrowser info 输出为准。

值得,而且它最值得研究的部分,不是"换一行 import 就能绕过风控"这种过度简化的宣传,而是它把现代浏览器自动化中的四个难题放在了一个工程体系里:

  1. 浏览器指纹的底层一致性 :不是只改 navigator.webdriver,而是试图在 Chromium 源码与编译产物层面处理 Canvas、WebGL、WebGPU、Audio、字体、屏幕、硬件、WebRTC、网络时序、CDP 行为等信号。
  2. 网络身份的一致性:代理出口 IP、时区、语言、WebRTC 暴露地址必须匹配,不能只换 IP,却保留 UTC、错误语言和本机 WebRTC 地址。
  3. 行为轨迹的一致性:鼠标不能瞬移,键盘不能零延迟灌值,滚动不能一步跳到底,元素操作前还要处理可见、稳定、可点击等状态。
  4. 生产工程能力:二进制自动下载、缓存、版本固定、回滚、签名验证、跨平台、持久化 Profile、同步/异步 API、诊断 CLI、Playwright/Puppeteer/.NET 兼容。

但同样必须把边界说清楚:

  • CloakBrowser 不是验证码识别器,它不能保证所有挑战都不出现。
  • 它不是代理池,不会替你管理 IP 质量、轮换策略和出口信誉。
  • 它不是"永远无法检测"的浏览器。反自动化是持续对抗,今天有效的组合未来可能失效。
  • 仓库包装层开源,不等于定制 Chromium 的全部补丁开源。Python、TypeScript、.NET 包装代码采用 MIT;CloakHQ 的构建配置、补丁和已编译二进制采用独立许可。
  • 它不应被用于未授权登录、撞库、批量注册、欺诈或绕过访问控制。本文代码以自有系统测试、授权 QA、监控、研究和合规自动化为场景。

如果你把它当成一个"浏览器身份一致性平台",它非常有研究价值;如果把它当成"一键通杀所有反爬"的魔法包,迟早会踩坑。

二、CloakBrowser 是什么:三层结构,而不是一个 Python 库

很多人安装后看到:

bash 复制代码
pip install cloakbrowser

便以为它只是另一个 Playwright 插件。实际上,它至少包含三层。

2.1 API 包装层

包装层提供了三套主要入口:

  • Python:同步 Playwright、异步 Playwright;
  • Node.js:Playwright 与 Puppeteer;
  • .NET 8:基于 Microsoft.Playwright 的客户端。

包装层负责:

  • 解析 headlessproxygeoiphumanize 等参数;
  • 找到或下载 CloakBrowser Chromium;
  • 组装启动参数;
  • 修正 Playwright/Puppeteer 的默认行为;
  • 注入 Humanize 行为层;
  • 管理浏览器、上下文和 Playwright Driver 生命周期;
  • 把许可证失败映射为清晰异常;
  • 提供 CLI、缓存、诊断与更新能力。

2.2 定制 Chromium 二进制

真正影响底层指纹的是定制 Chromium。项目说明将其描述为"源码级补丁",覆盖包括:

  • Canvas、ClientRects;
  • WebGL、WebGPU、GPU 参数;
  • AudioContext;
  • 字体与字体测量;
  • 屏幕、窗口、任务栏与视口几何;
  • User-Agent、Client Hints、平台信息;
  • CPU 核数、内存等硬件信号;
  • WebRTC ICE 地址;
  • 网络、代理与部分时序信号;
  • 自动化特征与 CDP 输入行为。

这与传统 playwright-stealth 的关键差异在于:后者通常是在页面创建后执行 JavaScript,重写可见属性;CloakBrowser 的目标是让值从浏览器内部产生,而不是在页面世界里临时伪造。

2.3 行为模拟层 Humanize

humanize=True 不是二进制补丁,而是包装层对 Playwright/Puppeteer 交互接口的改写:

  • 鼠标沿 Bézier 曲线移动;
  • 引入加减速、微抖动、短暂停顿和小概率越过目标;
  • 点击具有随机落点与按下持续时间;
  • 输入按字符产生,带随机键程、思考停顿和可选误触纠正;
  • 滚动模拟加速、巡航、减速与回调修正;
  • 操作前检查元素是否可见、稳定、启用、可编辑、能接收事件。

三、现代反自动化到底在检测什么?

理解 CloakBrowser,先要理解一个事实:现代反自动化系统不是只检查一个布尔值,而是在做多层信号融合

3.1 网络与信誉层

常见信号包括:

  • 出口 IP、ASN、IDC/住宅/移动网络类型;
  • IP 历史信誉、并发量、请求速率;
  • TLS ClientHello、JA3/JA4 类特征;
  • HTTP/2 参数、Header 顺序与优先级;
  • DNS、连接、代理、缓存相关特征;
  • IP 所在国家与浏览器时区、语言是否匹配。

即使浏览器指纹完全正常,低质量数据中心 IP 也可能直接被拒绝。反过来,住宅 IP 也不能修复明显的自动化行为。

3.2 浏览器静态指纹层

包括但不限于:

  • navigator.webdriver
  • navigator.pluginswindow.chrome
  • UA 与 Client Hints;
  • navigator.platform
  • hardwareConcurrencydeviceMemory
  • 屏幕尺寸、可用区域、颜色深度、DPR;
  • Canvas、WebGL、WebGPU、Audio;
  • 字体集合与文本宽高;
  • Codec、Speech、Media、Storage Quota;
  • WebRTC 暴露的网络地址。

难点不在"每个值都像真的",而在"这些值组成了一台可能真实存在的设备"。例如:

  • UA 说自己是 Windows 11;
  • 字体却只有 Alpine Linux 默认字体;
  • GPU 是 Apple Metal;
  • 屏幕是手机尺寸;
  • CPU 核数与内存组合极少见;
  • 外层窗口比内层页面还小。

每个值单看都合法,组合起来却不可能。

3.3 自动化框架与协议层

检测目标不仅是 DOM 属性,还包括:

  • Playwright/ChromeDriver 默认启动参数;
  • Headless 特有差异;
  • CDP 会话与命令使用模式;
  • page.evaluate() 产生的调用痕迹;
  • 非可信事件 isTrusted=false
  • 页面世界被注入或原生函数被重写;
  • 默认空插件、默认视口、自动化扩展等。

3.4 行为层

常见行为模型会观察:

  • 鼠标是否瞬移;
  • 轨迹曲率、速度变化、停顿分布;
  • 点击是否永远落在元素几何中心;
  • 键盘是否以恒定间隔输入;
  • 是否直接设置输入框 value;
  • 滚动是否跳跃;
  • 页面停留、导航顺序、交互节奏;
  • 页面还未稳定就立即点击。

3.5 历史状态层

浏览器是否像"使用过的浏览器",也很重要:

  • Cookie、LocalStorage、IndexedDB;
  • Cache、Service Worker;
  • 登录状态;
  • Profile 中的访问历史与持久数据;
  • 是否永远是一个全新的无痕环境。

因此,一次成功会话通常需要四层一致:

真正的目标不是"伪造更多值",而是减少矛盾。

四、从源码看 launch():一次启动经历了什么

Python 中最常见的代码只有几行:

python 复制代码
from cloakbrowser import launch

browser = launch()
page = browser.new_page()
page.goto("https://example.com")
browser.close()

launch() 内部并不简单。其主流程可以概括为:

下面逐步拆解。

4.1 第一步:确定实际要运行的二进制

ensure_binary() 的优先级大致是:

  1. 检查 CLOAKBROWSER_BINARY_PATH,允许直接使用本地构建;
  2. 读取显式 browser_versionCLOAKBROWSER_VERSION
  3. 解析许可证与发行通道;
  4. 查询本地缓存;
  5. 缓存不存在时下载正确平台的压缩包;
  6. 校验后解压到 ~/.cloakbrowser/
  7. 返回 chromechrome.exe 或 macOS App Bundle 中的可执行文件。

支持的平台标识包括 Linux x64、Linux ARM64、macOS x64/ARM64、Windows x64。具体平台可用的 Chromium 版本可能不同,因此不能只看一个全局版本号。

4.2 第二步:解析代理、GeoIP 和 WebRTC

当你提供:

python 复制代码
browser = launch(
    proxy="http://user:pass@proxy.example:8080",
    geoip=True,
)

包装层需要完成多件事:

  • 正确解析用户名、密码、主机、端口;
  • 对特殊字符进行 URL 编码;
  • 根据二进制能力决定使用 Chromium 原生内联认证,还是 Playwright 代理对象;
  • 通过代理访问 IP Echo 服务,得到实际出口 IP
  • 使用 GeoLite2 City 数据库查找时区和国家;
  • 将国家映射为 BCP 47 Locale;
  • 把出口 IP复用于 WebRTC 地址一致性处理;
  • 显式传入的 timezone/locale 优先于自动结果。

这里最容易犯的错误,是根据代理网关域名做地理定位。住宅代理或回连代理的网关可能在一个国家,而会话实际出口在另一个国家。CloakBrowser 优先通过代理访问 IP Echo 服务,拿到真正出口地址。

4.3 第三步:合并启动参数

默认参数不是简单列表拼接,而是按参数键去重:

  • 默认 Stealth 参数优先级最低;
  • 用户 args 可以覆盖默认值;
  • 专用参数 timezonelocale 优先级更高;
  • 扩展目录、窗口最大化等能力按条件加入。

默认会产生随机指纹种子,并根据宿主平台选择画像:

  • macOS 倾向使用原生 macOS 画像;
  • Linux/Windows 包装层默认使用 Windows 画像;
  • 每次启动随机 seed,除非显式固定。

同时,包装层会移除 Playwright 的部分默认参数,例如与自动化标记、SwiftShader 行为相关的参数,减少明显冲突。

4.4 第四步:处理 Viewport 与窗口几何

这是非常专业、也很容易被忽略的一点。

Playwright 创建页面时可能默认使用模拟视口。若真实外层窗口尺寸、浏览器 UI 区域和内层 viewport 不能自洽,就可能出现物理上不合理的关系,例如 outerWidth < innerWidth

CloakBrowser 的策略是:

  • headed 模式默认使用真实窗口,不额外模拟 viewport;
  • 新版本二进制若能在 headless 下保持几何一致,也使用 no_viewport
  • 旧版本 headless 使用固定视口,保证可预测性;
  • 用户显式传入 viewport 时尊重用户配置;
  • 避免同时传入 viewportno_viewport

这说明它的设计重点不是"把某个值改掉",而是尽量让一组几何量彼此一致。

4.5 第五步:启动并补齐生命周期

包装层调用 Playwright:

python 复制代码
pw.chromium.launch(
    executable_path=binary_path,
    headless=headless,
    args=chrome_args,
    ignore_default_args=IGNORE_DEFAULT_ARGS,
    ...
)

之后会替换 browser.close(),确保关闭 Chromium 的同时停止 Playwright Driver,防止进程与资源泄漏。

对于许可证限制,最新主分支还安装了运行期 guard:如果浏览器已完成 CDP 握手,随后因会话数限制退出,用户下一次调用本来只会看到模糊的 TargetClosedError。包装层通过每次启动独立的状态文件,把退出码转换为 CloakBrowserLicenseError,并覆盖 Browser、Context、Page 的关键调用面。

这类细节体现了一个成熟包装层与"几行启动脚本"的差距。

五、供应链安全:为什么不只是做一个 SHA-256

自动下载约数百 MB 的浏览器二进制,是一个高风险供应链动作。若下载服务器或镜像被入侵,攻击者可以替换二进制,同时替换同源的 SHA256SUMS。只检查同一个服务器提供的 Hash,并不能证明文件来自真正发布者。

CloakBrowser 的官方下载链路采用两阶段信任:

  1. 下载 SHA256SUMS
  2. 下载其分离签名 SHA256SUMS.sig
  3. 使用包装层内置的 Ed25519 公钥验证清单签名;
  4. 从已经认证的清单读取压缩包 Hash;
  5. 计算下载文件 SHA-256 并比较;
  6. 检查清单声明版本必须等于请求版本,防止镜像提供一个"签名合法但更旧"的版本;
  7. 验证通过后才解压。

伪代码如下:

python 复制代码
def verify_release(archive, manifest_bytes, signature, requested_version):
    public_key.verify(signature, manifest_bytes)

    manifest = parse_manifest(manifest_bytes)
    if manifest.version != requested_version:
        raise VerificationError("possible forced downgrade")

    expected = manifest.sha256[archive.name]
    actual = sha256_file(archive)
    if actual != expected:
        raise VerificationError("archive checksum mismatch")

这解决了三个不同问题:

  • 完整性:文件传输是否损坏;
  • 真实性:清单是否由发布者签发;
  • 新鲜度/版本绑定:是否被替换成旧但合法的版本。

下载时还使用临时文件,完成验证后再解压,避免半截文件进入缓存。对自建 CLOAKBROWSER_DOWNLOAD_URL,由于官方公钥无法证明第三方镜像内容,项目退回普通同源 Hash 机制;这时需要使用者自己建立可信发布链。

从架构角度看,这部分代码甚至比"指纹伪装"更值得复用到其他大型二进制分发系统中。

六、Humanize 不是随机 sleep:源码级拆解

官方给出的入口非常简单:

python 复制代码
browser = launch(humanize=True)

真正实现分成鼠标、键盘、滚动、可操作性检查与 API Patch 五部分。

6.1 鼠标:三次 Bézier 曲线 + 缓动

三次 Bézier 曲线公式为:

B ( t ) = ( 1 − t ) 3 P 0 + 3 ( 1 − t ) 2 t P 1 + 3 ( 1 − t ) t 2 P 2 + t 3 P 3 , t ∈ \[ 0 , 1 \] \] \[ B(t)=(1-t)\^3P_0+3(1-t)\^2tP_1+3(1-t)t\^2P_2+t\^3P_3,\\quad t\\in\[0,1\] \] \[B(t)=(1−t)3P0+3(1−t)2tP1+3(1−t)t2P2+t3P3,t∈\[0,1\]

其中:

  • P0 是起点;
  • P3 是目标点;
  • P1P2 是随机控制点;
  • 控制点沿路径法线方向加入偏移,产生自然曲率。

CloakBrowser 还叠加了:

  • ease-in-out 缓动,开始和结束慢,中段快;
  • 轨迹中段微小 wobble,起点和终点趋近于零;
  • 按 burst 成组发送移动事件,中间随机暂停;
  • 小概率越过目标数像素,再回到目标附近;
  • 点击落点不是固定中心,输入框更偏左侧,按钮在中部随机;
  • mouse.down()mouse.up() 之间有随机保持时间。

因此它模拟的不只是路径形状,还包括事件时间分布。

6.2 键盘:逐字符、邻键误触与可信事件

键盘层为每个字符执行:

  • 按键按下;
  • 随机 key hold;
  • 按键释放;
  • 字符间随机延迟;
  • 小概率长停顿;
  • 小概率选择键盘附近字符误触;
  • 延迟后 Backspace;
  • 再输入正确字符。

对中文、Emoji、Cyrillic 等非 ASCII 字符,使用 insert_text,避免错误模拟物理键盘映射。

对需要 Shift 的符号,优先通过 CDP Input.dispatchKeyEvent 发送事件,使事件具备浏览器认可的可信属性,同时避免在主页面世界中执行一段明显的合成事件脚本。

6.3 滚动:加速、巡航、减速

Humanize 滚动不会直接 scrollIntoView()

  1. 计算目标元素 bounding box;
  2. 检查它是否已经处于视口合理区域;
  3. 将鼠标移动到页面滚动区域;
  4. 计算目标中心与期望落点的距离;
  5. 前几步使用较慢、较小的滚轮增量;
  6. 中间进入较快巡航;
  7. 临近目标减速;
  8. 每隔几步重新读取元素位置;
  9. 小概率越过,再反向纠正;
  10. 最后等待页面稳定。

6.4 Isolated World 与 Actionability

Humanize 需要判断元素是不是输入框、是否聚焦、是否可操作。直接大量调用 page.evaluate() 会进入主页面执行环境,可能被页面重写的 querySelector 或监控代码观察。

项目为此建立 CDP Isolated World:

  • 为主 Frame 创建隔离执行上下文;
  • 在隔离世界读取 DOM;
  • 导航后使 context ID 失效并自动重建;
  • CDP 不可用时才回退到 page.evaluate()

同时,它在点击、输入前进行 Actionability 检查,接近 Playwright 自身的"可见、稳定、启用、可接收事件"语义。这样既能提高成功率,也避免元素仍在动画时立即点击产生机械行为。

6.5 Humanize 的代价

Humanize 会显著降低吞吐量。生产系统不应无脑对每个动作启用最慢 preset。更合理的策略是:

  • 页面导航、静态读取不必全部模拟;
  • 登录、表单、挑战前后的关键交互使用 Humanize;
  • 内部后台、无行为检测页面使用原生高速 API;
  • 通过 page._original 或 per-call 配置进行分级;
  • 对延迟敏感场景使用自定义参数,而不是一律 careful

七、安装与诊断:先把环境跑对,再谈效果

7.1 Python

bash 复制代码
python -m venv .venv

# Linux / macOS
source .venv/bin/activate

# Windows PowerShell
# .\.venv\Scripts\Activate.ps1

python -m pip install -U pip
pip install cloakbrowser

需要 GeoIP:

bash 复制代码
pip install 'cloakbrowser[geoip]'

Playwright 的浏览器无需另装,但 Linux 仍可能需要系统依赖:

bash 复制代码
playwright install-deps chromium

7.2 Node.js

bash 复制代码
npm install cloakbrowser playwright-core

Puppeteer:

bash 复制代码
npm install cloakbrowser puppeteer-core

7.3 .NET

bash 复制代码
dotnet add package CloakBrowser

7.4 CLI 诊断

bash 复制代码
cloakbrowser install
cloakbrowser info
cloakbrowser info --json
cloakbrowser update

推荐把 info --json 纳入部署探针,记录:

  • Wrapper 版本;
  • 实际启动的 Chromium 版本;
  • 平台标签;
  • 缓存路径;
  • 许可证层级;
  • 字体情况;
  • GeoIP 数据库状态;
  • 缺失的 Linux 共享库;
  • Stable/Preview 实际解析结果。

不要只在本机测试通过后,就假设 Docker、CI、Kubernetes 中完全相同。字体、显示服务、共享库和 CPU 架构都会造成差异。

八、最小可运行示例:授权页面采集

下面使用 example.com,展示同步 API、异常处理和输出文件。

python 复制代码
from __future__ import annotations

import json
import logging
from pathlib import Path
from typing import Any

from cloakbrowser import launch

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(name)s: %(message)s",
)
logger = logging.getLogger("cloak-demo")


def collect_page(url: str, output_dir: Path) -> dict[str, Any]:
    output_dir.mkdir(parents=True, exist_ok=True)
    browser = launch(
        headless=True,
        humanize=False,
    )

    try:
        page = browser.new_page()
        response = page.goto(url, wait_until="domcontentloaded", timeout=30_000)

        result = {
            "url": page.url,
            "title": page.title(),
            "status": response.status if response else None,
            "h1": page.locator("h1").first.text_content(),
        }

        page.screenshot(path=str(output_dir / "page.png"), full_page=True)
        (output_dir / "result.json").write_text(
            json.dumps(result, ensure_ascii=False, indent=2),
            encoding="utf-8",
        )
        return result
    finally:
        browser.close()


if __name__ == "__main__":
    data = collect_page("https://example.com", Path("artifacts/example"))
    logger.info("result=%s", data)

这段代码没有使用"万能重试",因为浏览器自动化中的失败需要分类:

  • DNS/网络超时:可以有限重试;
  • 401/403:不应盲目重试,应先检查授权、代理信誉或访问政策;
  • 选择器失效:属于页面结构变化;
  • 许可证限制:应释放并发或调整套餐;
  • 浏览器崩溃:需要诊断二进制、共享库、内存和 GPU;
  • 业务数据为空:应做语义校验,而不只是 HTTP 200。

九、生产级示例:持久化 Profile、固定身份与重试分类

下面构建一个更完整的"授权站点监控器"。它具备:

  • Profile 持久化;
  • 固定指纹 seed;
  • 浏览器与页面生命周期管理;
  • 只对可恢复错误重试;
  • JSON 与截图输出;
  • 代理通过环境变量注入,避免写入源码;
  • 每个 Profile 只允许一个进程使用。
python 复制代码
from __future__ import annotations

import json
import logging
import os
import random
import time
from dataclasses import asdict, dataclass
from pathlib import Path
from typing import Any

from cloakbrowser import launch_persistent_context
from playwright.sync_api import Error as PlaywrightError
from playwright.sync_api import TimeoutError as PlaywrightTimeoutError

logger = logging.getLogger("authorized-monitor")


@dataclass(frozen=True)
class MonitorConfig:
    url: str
    profile_dir: Path
    output_dir: Path
    fingerprint_seed: int
    proxy: str | None = None
    headless: bool = True
    max_attempts: int = 3


class RetryableNavigationError(RuntimeError):
    pass


def is_retryable_status(status: int | None) -> bool:
    return status is None or status in {408, 425, 429, 500, 502, 503, 504}


def backoff(attempt: int) -> float:
    base = min(2 ** (attempt - 1), 8)
    return base + random.uniform(0.1, 0.8)


def run_once(cfg: MonitorConfig) -> dict[str, Any]:
    cfg.profile_dir.mkdir(parents=True, exist_ok=True)
    cfg.output_dir.mkdir(parents=True, exist_ok=True)

    context = launch_persistent_context(
        cfg.profile_dir,
        headless=cfg.headless,
        proxy=cfg.proxy,
        geoip=bool(cfg.proxy),
        humanize=True,
        human_preset="default",
        args=[f"--fingerprint={cfg.fingerprint_seed}"],
    )

    try:
        page = context.pages[0] if context.pages else context.new_page()
        response = page.goto(
            cfg.url,
            wait_until="domcontentloaded",
            timeout=45_000,
        )
        status = response.status if response else None

        if is_retryable_status(status):
            raise RetryableNavigationError(f"retryable status: {status}")
        if status is not None and status >= 400:
            raise RuntimeError(f"non-retryable HTTP status: {status}")

        page.locator("body").wait_for(state="visible", timeout=15_000)

        result = {
            "url": page.url,
            "title": page.title(),
            "status": status,
            "captured_at": int(time.time()),
        }

        stamp = str(result["captured_at"])
        page.screenshot(
            path=str(cfg.output_dir / f"{stamp}.png"),
            full_page=True,
        )
        (cfg.output_dir / f"{stamp}.json").write_text(
            json.dumps(result, ensure_ascii=False, indent=2),
            encoding="utf-8",
        )
        return result
    finally:
        context.close()


def run_with_retry(cfg: MonitorConfig) -> dict[str, Any]:
    last_error: Exception | None = None

    for attempt in range(1, cfg.max_attempts + 1):
        try:
            return run_once(cfg)
        except (PlaywrightTimeoutError, RetryableNavigationError) as exc:
            last_error = exc
            if attempt == cfg.max_attempts:
                break
            delay = backoff(attempt)
            logger.warning(
                "attempt %s/%s failed: %s; retry in %.2fs",
                attempt,
                cfg.max_attempts,
                exc,
                delay,
            )
            time.sleep(delay)
        except PlaywrightError:
            # 浏览器协议错误不一定可恢复,保留完整堆栈直接退出。
            logger.exception("browser protocol failure")
            raise

    raise RuntimeError("monitor failed after retries") from last_error


if __name__ == "__main__":
    logging.basicConfig(level=logging.INFO)

    config = MonitorConfig(
        url=os.environ.get("TARGET_URL", "https://example.com"),
        profile_dir=Path("profiles/site-a"),
        output_dir=Path("artifacts/site-a"),
        fingerprint_seed=42069,
        proxy=os.environ.get("HTTPS_PROXY"),
        headless=os.environ.get("HEADED") != "1",
    )

    logger.info("config=%s", asdict(config) | {"proxy": bool(config.proxy)})
    logger.info("result=%s", run_with_retry(config))

为什么 Profile、IP、Seed 应绑定?

固定 seed 只固定浏览器指纹,不会自动固定:

  • Cookie;
  • LocalStorage;
  • 代理出口;
  • 账号;
  • 语言和时区;
  • 页面历史。

生产系统应把它们建模为一个 Session Identity:

text 复制代码
session_id
├── profile_dir
├── fingerprint_seed
├── proxy_id / exit_region
├── locale + timezone
├── account_id(若有)
└── last_used_at

同一 Profile 今天从新加坡访问、明天突然从德国访问;或同一账号每次都换设备指纹,都可能比固定身份更异常。

十、异步并发:重点不是"开得越多越好"

浏览器实例非常重。与普通 HTTP 客户端不同,一个 Chromium 会占用多个进程、内存、文件描述符和共享内存。并发应受三层约束:

  • 许可证允许的会话数;
  • 机器资源;
  • 目标系统授权的请求速率。

下面示例使用一个浏览器、多个独立 Context,并用 Semaphore 限制并发。适用于你自有站点的批量回归测试;若不同任务需要完全独立的代理和进程级指纹参数,应改为多个 Browser Worker。

python 复制代码
from __future__ import annotations

import asyncio
import json
from pathlib import Path
from typing import Iterable

from cloakbrowser import launch_async


async def fetch_one(browser, url: str, sem: asyncio.Semaphore) -> dict:
    async with sem:
        context = await browser.new_context()
        try:
            page = await context.new_page()
            response = await page.goto(
                url,
                wait_until="domcontentloaded",
                timeout=30_000,
            )
            return {
                "url": page.url,
                "title": await page.title(),
                "status": response.status if response else None,
            }
        finally:
            await context.close()


async def collect(urls: Iterable[str], concurrency: int = 3) -> list[dict]:
    browser = await launch_async(headless=True)
    sem = asyncio.Semaphore(concurrency)

    try:
        tasks = [asyncio.create_task(fetch_one(browser, url, sem)) for url in urls]
        return await asyncio.gather(*tasks)
    finally:
        await browser.close()


async def main() -> None:
    urls = [
        "https://example.com",
        "https://example.com/?case=2",
        "https://example.com/?case=3",
    ]
    results = await collect(urls, concurrency=2)
    Path("results.json").write_text(
        json.dumps(results, ensure_ascii=False, indent=2),
        encoding="utf-8",
    )


if __name__ == "__main__":
    asyncio.run(main())

一个 Browser 多 Context,还是多个 Browser?

一个 Browser,多 Context:

  • 启动快、内存低;
  • Context 间 Cookie 隔离;
  • 但进程级启动参数共享;
  • 某些时区、Locale、平台画像属于进程级;
  • 一个浏览器崩溃会影响所有 Context。

多个 Browser:

  • 每个 Worker 可拥有独立代理、seed、时区和启动参数;
  • 故障隔离更好;
  • 资源消耗大;
  • 启动成本高;
  • 必须受许可证和机器容量约束。

生产中通常采用"固定数量 Browser Worker + 每个 Worker 少量 Context"的混合模型,而不是每个 URL 临时启动一个 Chromium。

十一、自建一致性审计:不要只看一个检测网站

下面代码收集常见浏览器信号,适合在你自己的测试页面或受控环境中做回归对比。

python 复制代码
from __future__ import annotations

import json
from pathlib import Path

from cloakbrowser import launch

COLLECT_SCRIPT = r"""
() => {
  const canvas = document.createElement('canvas');
  const gl = canvas.getContext('webgl');
  const dbg = gl && gl.getExtension('WEBGL_debug_renderer_info');

  return {
    userAgent: navigator.userAgent,
    platform: navigator.platform,
    webdriver: navigator.webdriver,
    languages: navigator.languages,
    language: navigator.language,
    hardwareConcurrency: navigator.hardwareConcurrency,
    deviceMemory: navigator.deviceMemory,
    plugins: navigator.plugins.length,
    timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
    screen: {
      width: screen.width,
      height: screen.height,
      availWidth: screen.availWidth,
      availHeight: screen.availHeight,
      colorDepth: screen.colorDepth,
      pixelDepth: screen.pixelDepth,
    },
    window: {
      innerWidth,
      innerHeight,
      outerWidth,
      outerHeight,
      devicePixelRatio,
    },
    webgl: gl ? {
      vendor: dbg ? gl.getParameter(dbg.UNMASKED_VENDOR_WEBGL) : null,
      renderer: dbg ? gl.getParameter(dbg.UNMASKED_RENDERER_WEBGL) : null,
    } : null,
  };
}
"""


def audit() -> dict:
    browser = launch(headless=True)
    try:
        page = browser.new_page()
        page.goto("https://example.com", wait_until="domcontentloaded")
        data = page.evaluate(COLLECT_SCRIPT)

        warnings: list[str] = []
        win = data["window"]
        if win["outerWidth"] < win["innerWidth"]:
            warnings.append("outerWidth < innerWidth")
        if win["outerHeight"] < win["innerHeight"]:
            warnings.append("outerHeight < innerHeight")
        if data["webdriver"] is True:
            warnings.append("navigator.webdriver=true")
        if data["plugins"] == 0:
            warnings.append("empty plugin list")

        return {"signals": data, "warnings": warnings}
    finally:
        browser.close()


report = audit()
Path("fingerprint-audit.json").write_text(
    json.dumps(report, ensure_ascii=False, indent=2),
    encoding="utf-8",
)
print(json.dumps(report, ensure_ascii=False, indent=2))

更完整的企业级审计应同时采集:

  • 浏览器 JS 信号;
  • 服务器看到的 Header;
  • TLS/HTTP2 指纹;
  • 出口 IP 与 ASN;
  • DNS 与 WebRTC 地址;
  • 页面行为事件序列;
  • 同一 seed 多次启动是否稳定;
  • 不同 seed 是否出现不可能组合;
  • headed/headless/Docker/裸机之间的差异。

不要用一个站点的一次"绿色"结果宣布系统成功。检测服务自身也会更新,且不同服务使用的模型不同。

十二、JavaScript / TypeScript:Playwright 用法

typescript 复制代码
import { launchPersistentContext } from 'cloakbrowser';
import fs from 'node:fs/promises';

async function main(): Promise<void> {
  const context = await launchPersistentContext({
    userDataDir: './profiles/site-a',
    headless: true,
    humanize: true,
    humanPreset: 'default',
    args: ['--fingerprint=42069'],
  });

  try {
    const pages = context.pages();
    const page = pages[0] ?? await context.newPage();
    const response = await page.goto('https://example.com', {
      waitUntil: 'domcontentloaded',
      timeout: 30_000,
    });

    const result = {
      url: page.url(),
      title: await page.title(),
      status: response?.status() ?? null,
    };

    await page.screenshot({ path: 'example.png', fullPage: true });
    await fs.writeFile('example.json', JSON.stringify(result, null, 2), 'utf8');
  } finally {
    await context.close();
  }
}

main().catch((error: unknown) => {
  console.error(error);
  process.exitCode = 1;
});

Node 包还暴露两个很有价值的组合式函数:

  • buildLaunchOptions():只构建 CloakBrowser 的 Playwright LaunchOptions,不直接启动;
  • humanizeBrowser():对已有 Browser 应用 Humanize。

这意味着你可以把它集成到已有的浏览器框架,而不必完全交出启动控制权:

typescript 复制代码
import { chromium } from 'playwright-core';
import { buildLaunchOptions, humanizeBrowser } from 'cloakbrowser';

const cloakOptions = {
  headless: true,
  humanize: true,
  args: ['--fingerprint=20260801'],
};

const launchOptions = await buildLaunchOptions(cloakOptions);
const browser = await chromium.launch({
  ...launchOptions,
  timeout: 60_000,
});

await humanizeBrowser(browser, cloakOptions);

这是良好的库设计:既提供"一键入口",又提供可组合的低层构件。

十三、Puppeteer 可以用,但要理解协议差异

入口为:

typescript 复制代码
import { launch } from 'cloakbrowser/puppeteer';

const browser = await launch({
  headless: true,
  humanize: true,
});

try {
  const page = await browser.newPage();
  await page.goto('https://example.com');
} finally {
  await browser.close();
}

Puppeteer 包装层同样处理:

  • 默认 viewport;
  • 二进制路径;
  • 指纹参数;
  • HTTP/SOCKS 代理;
  • 旧二进制的代理认证回退;
  • GeoIP 与 WebRTC;
  • Humanize;
  • 持久化 userDataDir
  • 许可证错误映射。

但 Playwright 与 Puppeteer 的 CDP 使用方式、页面初始化行为和默认配置不同。项目文档本身也更推荐在高敏感场景优先使用 Playwright。工程上不要因为两者 API 都能启动,就假设其网络与协议痕迹完全等价。

十四、Docker、CI 与云环境

最小 smoke test:

bash 复制代码
docker run --rm cloakhq/cloakbrowser cloaktest

在企业部署中,更推荐把二进制下载放到镜像构建阶段,而不是每个容器启动时下载:

dockerfile 复制代码
FROM python:3.12-slim

ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1 \
    CLOAKBROWSER_CACHE_DIR=/opt/cloak-cache

RUN apt-get update \
    && apt-get install -y --no-install-recommends \
       fonts-noto-color-emoji \
       fonts-freefont-ttf \
       fonts-unifont \
       fonts-wqy-zenhei \
    && rm -rf /var/lib/apt/lists/*

RUN pip install --no-cache-dir cloakbrowser \
    && playwright install-deps chromium \
    && cloakbrowser install \
    && cloakbrowser info --quick

WORKDIR /app
COPY app.py /app/app.py

CMD ["python", "/app/app.py"]

Docker 中必须关注的资源

  • /dev/shm:Chromium 多进程共享内存不足会崩溃;
  • 内存 Limit:不能只看主进程 RSS;
  • PID Limit:一个浏览器会派生多个子进程;
  • 文件描述符;
  • 字体;
  • 时区数据库;
  • Xvfb/显示服务,如果必须 headed;
  • Profile 持久卷;
  • 缓存卷;
  • 容器架构与二进制平台是否匹配。

建议:

bash 复制代码
docker run --rm \
  --shm-size=1g \
  -v cloak-cache:/root/.cloakbrowser \
  -v "$PWD/profiles:/app/profiles" \
  your-image:latest

不要多个容器同时写同一 Profile

Chromium Profile 不是并发数据库。多个进程共享一个 userDataDir 容易出现:

  • SingletonLock 冲突;
  • Cookie DB 锁;
  • Profile 损坏;
  • 状态互相覆盖;
  • 身份串线。

正确做法是为每个 Session 分配独占 Profile,并由调度器维护租约。

十五、推荐的生产架构

一个稳健的浏览器自动化平台可以拆成以下组件:

text 复制代码
API / Scheduler
      │
      ▼
Task Queue ──────────────── Dead Letter Queue
      │
      ▼
Session Allocator
  ├── profile lease
  ├── fingerprint seed
  ├── proxy binding
  ├── locale/timezone
  └── license slot
      │
      ▼
Browser Worker Pool
  ├── Browser 1 -> Context/Page
  ├── Browser 2 -> Context/Page
  └── Browser N -> Context/Page
      │
      ├── Artifact Store:截图、HAR、HTML、JSON
      ├── Metrics:启动耗时、导航耗时、崩溃、内存
      └── Audit Log:授权目标、任务来源、会话身份

15.1 Session Allocator 的职责

它不能只"拿一个代理",而应原子分配:

json 复制代码
{
  "session_id": "sess_01J...",
  "profile_dir": "/profiles/sess_01J...",
  "fingerprint_seed": 42069,
  "proxy_id": "proxy_sg_008",
  "timezone": "Asia/Singapore",
  "locale": "en-SG",
  "browser_version": "resolved-at-runtime",
  "expires_at": 1785628800
}

15.2 Worker 池应有背压

不要让请求直接无限创建浏览器。至少需要:

  • 有界队列;
  • Browser 启动并发限制;
  • 页面任务超时;
  • 每个 Worker 最大任务数;
  • 内存水位驱逐;
  • 浏览器崩溃自动重建;
  • Profile 锁与租约;
  • 优雅停机。

15.3 可观测性指标

建议记录:

  • browser_launch_seconds
  • page_navigation_seconds
  • browser_crash_total
  • page_timeout_total
  • license_denial_total
  • binary_download_total
  • binary_verify_failure_total
  • geoip_resolution_seconds
  • worker_memory_bytes
  • active_browser_sessions
  • profile_lock_conflict_total

把"被挑战/被拒绝"当成普通业务指标,而不是只写在日志文本里。

十六、性能与容量评估

16.1 冷启动成本

首次启动包括:

  • 下载约数百 MB 浏览器;
  • 校验签名与 Hash;
  • 解压;
  • 首次 GeoIP 数据库下载约几十 MB;
  • Chromium 初始化;
  • Profile 初始化;
  • 字体缓存加载。

因此:

  • CI 镜像应预下载;
  • 运行节点应保留缓存卷;
  • 不要把冷启动延迟计入普通页面 SLA;
  • GeoIP 数据库最好在镜像或初始化阶段准备。

16.2 Humanize 成本

假设输入 50 个字符,平均每字符 70ms,再叠加键程、停顿和偶发误触,可能需要 4~8 秒;一次滚动与点击也可能增加数百毫秒到数秒。它不适合所有页面全部开启。

16.3 内存模型

浏览器内存近似:

text 复制代码
总内存 ≈ Browser 基础开销
      + Renderer 数量 × 单 Renderer 内存
      + 页面资源
      + Profile / Cache
      + GPU / Shared Memory

页面是否包含视频、Canvas、WebGL、大量图片和前端框架,差异很大。容量规划必须用真实业务页面压测,不能拿 example.com 推算。

16.4 版本更新风险

CloakBrowser 同时受三条版本链影响:

  • Wrapper 版本;
  • 定制 Chromium 二进制版本;
  • Playwright/Puppeteer 版本。

升级前应做矩阵回归:

维度 示例
OS Linux x64 / Windows / macOS
模式 Headless / Headed
代理 无代理 / HTTP / SOCKS5 / 认证代理
Profile 临时 / 持久化
API Python Sync / Async / Node Playwright / Puppeteer
业务 登录、表单、文件下载、Shadow DOM、Iframe、上传

生产环境应支持 CLOAKBROWSER_VERSION 固定版本和快速回滚。

十七、常见问题与真正原因

17.1 安装成功,但第一次运行很慢

正常。可能正在下载并解压二进制;启用 GeoIP 后还会下载数据库。查看日志和缓存目录,不要在下载过程中重复启动多个进程。

17.2 Linux 上效果比 Windows 差

重点检查字体。Linux 默认画像若表现为 Windows,却缺少 Segoe UI、Calibri、Consolas 等 Windows 字体,会形成明显矛盾。基础 Noto/CJK/Emoji 字体只能解决最小渲染问题,不能等价于完整 Windows 字体环境。

17.3 Headed 在服务器上无法启动

需要 Xvfb 或真实显示服务:

bash 复制代码
sudo apt-get install -y xvfb
Xvfb :99 -screen 0 1920x1080x24 &
export DISPLAY=:99

同时确认容器内字体、共享内存和沙箱配置。

17.4 geoip=True 仍然是错误地区

检查:

  • 代理是否真正生效;
  • 认证信息是否包含未编码特殊字符;
  • IP Echo 是否能通过代理访问;
  • 代理网关 IP 与实际出口 IP 是否不同;
  • GeoIP 数据库是否存在且未损坏;
  • 是否显式传入了 timezone/locale 覆盖自动值。

17.5 同一 Node 进程启动多个不同代理,却得到相同地区

历史版本出现过连接池复用导致出口身份串线的问题,v0.5.3 已修复相关逻辑。生产环境仍应将"实际出口 IP"写入日志,并在每次会话启动后做一致性校验。

17.6 持久化 Profile 无法启动

通常是同一目录已被另一个 Chromium 占用。不要删除锁文件强行启动;先确认是否存在活进程,再通过租约管理释放 Profile。

17.7 指纹 seed 固定了,但身份仍不稳定

Seed 只影响一部分浏览器画像。以下仍可能变化:

  • IP;
  • 时区/语言;
  • 浏览器版本;
  • 字体环境;
  • Headed/Headless;
  • 视口;
  • Profile;
  • 扩展;
  • GPU/宿主机。

必须固定完整 Session Identity,而不是只固定一个数字。

17.8 下载校验失败

不要通过关闭验证"先跑起来"。官方路径的签名验证失败意味着真实性无法确认,应:

  • 检查系统时间、网络与代理;
  • 清理临时下载后重试;
  • 检查是否被企业代理替换内容;
  • 核对版本;
  • 查看项目 Issue;
  • 在可信网络重新下载。

17.9 CLOAKBROWSER_BINARY_PATH 指向普通 Chrome,可以吗?

包装层能够启动,但普通 Chrome 不具备 CloakBrowser 定制补丁。此选项更适合本地开发、调试或自有构建,不应把"API 能跑"误认为"具备相同指纹能力"。

十八、许可证与商业部署:最容易被误读的一章

18.1 Wrapper 与 Binary 是两套许可

  • 仓库中的 Python、JavaScript、.NET 包装代码:MIT;
  • CloakHQ 发布的定制 Chromium Binary、构建配置和补丁:独立 Binary License。

这意味着你可以自由学习和修改包装层,但不能自然推导出你有权:

  • 重新分发官方二进制;
  • 把它打包进面向第三方销售的产品;
  • 作为 Browser-as-a-Service 提供;
  • 逆向、反编译定制二进制;
  • 绕过许可证或并发限制。

18.2 内部使用与 SaaS 的分界

独立许可允许企业在内部基础设施中运行,用于自己的自动化、研究、数据处理和业务交付;但如果把浏览器能力本身暴露给第三方控制,或作为托管浏览器、客户可配置工作流、OEM 能力提供,通常需要单独授权。

举例:

  • 你的公司使用 CloakBrowser 采集公开、获授权的数据,最终向客户交付报告:可能仍属于内部使用;
  • 客户可以在你的平台输入任意 URL、代理、脚本并远程控制浏览器:更接近 SaaS/OEM,需要额外许可;
  • 在内部 Docker 镜像中保存二进制:许可文本允许内部基础设施使用,但不能因此向外分发该镜像。

上线前应由法务结合最新 License 审核,而不是只看仓库首页的 MIT Badge。

18.3 数据与隐私

启用 GeoIP 时会访问第三方 IP Echo 服务;许可证验证与会话计数可能与 CloakHQ 服务通信。企业环境应评估:

  • 出口 IP 是否属于敏感信息;
  • 代理凭据是否会出现在日志;
  • License 请求是否需走代理;
  • Profile 是否包含账号 Cookie;
  • 截图、HTML、HAR 是否含个人数据;
  • 缓存和 Profile 的保留期限;
  • 是否需要磁盘加密与密钥管理。

十九、与其他方案的架构对比

方案 浏览器引擎 主要改动位置 API 生态 典型优势 典型限制
原生 Playwright Chromium/Firefox/WebKit 无 Stealth 非常成熟 稳定、测试能力强 自动化默认信号明显
playwright-stealth Chromium JS 注入/属性覆盖 Playwright 轻量、安装快 页面世界可观察、维护跟随浏览器变化
undetected-chromedriver Chrome Driver/配置/启动修补 Selenium Selenium 生态成熟 与 Playwright 不兼容,版本耦合明显
Camoufox Firefox 定制 Firefox Playwright 方向 Firefox 路线、指纹能力强 与 Chromium 行为和站点兼容性不同
CloakBrowser Chromium 定制二进制 + 包装层 Playwright/Puppeteer/.NET Chromium 生态、跨语言、Humanize、供应链校验 Binary 非完全开源、许可与更新依赖、无法保证长期不被识别

这里没有绝对赢家。选择取决于:

  • 目标站点是否要求 Chromium;
  • 是否依赖 Chrome 扩展;
  • 是否需要 Playwright;
  • 是否接受专有 Binary;
  • 是否能持续跟进版本;
  • 是否需要多语言 SDK;
  • 是否需要行为模拟;
  • 是否有合规授权。

二十、对项目的技术评价:亮点与不足

20.1 真正优秀的地方

第一,工程完整度高。

不是只提供一个二进制下载地址,而是把版本、缓存、签名、回滚、GeoIP、代理、Profile、CLI、三种语言包装起来。

第二,重视一致性。

Viewport、时区、Locale、WebRTC、字体、平台画像等细节说明开发者理解"矛盾比单值更危险"。

第三,Humanize 源码可读。

鼠标、键盘、滚动算法不是黑盒,可调参数集中,便于二次开发和性能取舍。

第四,错误处理在持续成熟。

最近版本持续修复代理身份串线、Windows 控制台、许可证握手后错误等边缘问题,说明项目已经进入生产工程打磨阶段。

第五,供应链验证设计认真。

Ed25519 签名、Hash、版本绑定、临时文件和失败关闭,比多数自动下载型工具更完善。

20.2 必须保持清醒的地方

第一,"源码级补丁"不等于补丁源码全部公开。

仓库主要可审计的是包装层;定制 Chromium 的核心补丁与构建产物受独立许可。安全敏感企业需要评估供应商依赖和二进制审计能力。

第二,官方检测结果属于项目方自测。

截图和表格可以作为参考,但不能替代你的真实目标、真实代理、真实部署环境和持续回归。

第三,反自动化没有永久解。

浏览器升级、检测模型、代理信誉和目标业务规则都会变化。

第四,平台画像可能与宿主环境冲突。

例如 Linux 默认模拟 Windows,需要字体、GPU、屏幕和窗口几何共同配合。

第五,Humanize 不是行为身份的全部。

真实用户还有阅读节奏、页面路径、历史状态、内容偏好和任务上下文。随机曲线只能覆盖其中一部分。

第六,商业许可要单独核对。

Wrapper 的 MIT 并不覆盖 Binary 的再分发、SaaS 和 OEM 使用。

二十一、什么时候适合使用 CloakBrowser?

适合:

  • 自有网站的反自动化回归测试;
  • 获授权的第三方页面自动化;
  • 需要 Chromium 与 Playwright 生态的 QA;
  • 合规的数据采集、价格监控、内容监控;
  • 浏览器 Agent 的底层执行器;
  • 需要持久化 Profile、扩展和复杂页面交互的任务;
  • 研究浏览器指纹一致性;
  • 在受控环境中比较不同浏览器自动化方案。

不适合:

  • 未授权账户登录;
  • Credential Stuffing;
  • 批量虚假注册;
  • 绕过付费、身份验证或访问控制;
  • 欺诈、广告作弊;
  • 不允许自动化访问的高风险系统;
  • 无法接受专有二进制和持续订阅依赖的项目;
  • 只需要普通网页测试、完全没有 Stealth 需求的场景。

普通内部系统 E2E 测试,原生 Playwright 往往更简单、更可控;只有当你确实面对指纹一致性、Headless 环境差异、授权的反自动化测试时,CloakBrowser 的复杂度才有价值。

二十二、总结

CloakBrowser 的核心价值可以浓缩成一句话:

它不是在页面加载后"伪装几个 JavaScript 属性",而是试图把浏览器二进制、启动参数、网络位置、窗口几何、用户行为和持久状态组合成一个自洽会话。

从技术学习角度,最值得掌握的不是某几个 --fingerprint-* 参数,而是下面五条原则:

  1. 一致性优先于随机性:随机越多不一定越自然,固定身份往往更合理;
  2. 网络、浏览器、行为、状态必须统一建模
  3. 包装层也需要生产级工程:下载、验证、缓存、回滚、错误映射缺一不可;
  4. Humanize 应按风险分级,不应成为全局性能税
  5. 检测能力和授权边界都在变化,必须持续测试与持续合规。

如果要把它用于生产,推荐从下面这条最小路线开始:

text 复制代码
固定 Wrapper 版本
→ 运行 cloakbrowser info
→ 在受控测试页建立一致性基线
→ 绑定 Profile + Seed + Proxy + Locale
→ 小并发 Worker 池
→ 记录出口 IP、版本、崩溃与挑战指标
→ 灰度升级与快速回滚
→ 定期复核 Binary License

做到这些,你使用的才不是一个"反检测脚本",而是一套可维护、可审计、可回滚的浏览器自动化基础设施。

附录 A:常用配置速查

bash 复制代码
# 缓存目录
export CLOAKBROWSER_CACHE_DIR=/data/cloak-cache

# 本地二进制覆盖
export CLOAKBROWSER_BINARY_PATH=/opt/cloak/chrome

# 固定二进制版本
export CLOAKBROWSER_VERSION=146.0.7680.177.5

# Preview 通道
export CLOAKBROWSER_RELEASE_CHANNEL=preview

# 关闭自动更新
export CLOAKBROWSER_AUTO_UPDATE=false

# GeoIP 超时
export CLOAKBROWSER_GEOIP_TIMEOUT_SECONDS=5

# 许可证
export CLOAKBROWSER_LICENSE_KEY=cb_xxxxxxxx

附录 B:常用 Python 入口

python 复制代码
from cloakbrowser import (
    launch,
    launch_async,
    launch_context,
    launch_context_async,
    launch_persistent_context,
    launch_persistent_context_async,
)

附录 C:官方 README 中的测试截图

以下图片来自项目官方 README,是维护者给出的特定版本、特定环境测试结果。它们不构成对任意目标、任意部署环境的保证。

项目方展示的 Turnstile 测试

项目方展示的 reCAPTCHA v3 分数

项目方展示的 BrowserScan 结果

项目方展示的 FingerprintJS 测试

项目方展示的行为检测结果

参考资料

相关推荐
晚安日记wanna12 分钟前
MySQL 主从延迟别只答并行复制
数据库·面试·架构
狼与自由36 分钟前
MCP协议理解
人工智能·架构
小程序设计1 小时前
Easy-Shop商城小程序漏洞挖掘与防护研究
网络·安全
天远API2 小时前
零信任架构实战:基于天远单人婚姻查询构建自动化联合贷款审计网关
java·人工智能·架构·自动化
2601_966377133 小时前
等保2.0最新标准新增安全数据处理要求后怎么办?等保一体机数据安全模块来助力
运维·网络·安全·等保一体机
海宇AI3 小时前
零信任架构实战:基于海宇公安二要素认证即时版构建自动化灵活用工准入网关
运维·人工智能·架构·自动化
执念WRD3 小时前
Fastjson 1.2.83 RCE 漏洞利用
网络·安全·网络安全
xixiaoyunya3 小时前
局域网多电脑文件互备:从传统痛点到自动化方案的技术路径
网络·安全
荆棘鸟智能3 小时前
AI算法持续学习与迭代怎么做?从数据闭环到灰度发布的MLOps工程实践
人工智能·算法·架构·边缘计算