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 测试

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

参考资料

相关推荐
德迅云安全杨德俊1 小时前
云原生容器安全新标杆
安全·云原生
虹科网络安全8 小时前
解读CRA横向标准EN 40000系列:从法规文本到工程落地 | CRA合规之路(七)
网络·安全·web安全
DevSecOps选型指南10 小时前
中国版Mythos,为何是悬镜安全灵脉CodeAI?
人工智能·安全
凌虚14 小时前
面向 MySQL 用户的 PostgreSQL 快速上手指南
数据库·后端·架构
德迅云安全杨德俊14 小时前
应用加速:一站式解决企业跨域网络加速与安全双重难题
网络·安全·ddos
努力进修15 小时前
KFS 全链路并行架构:异构增量同步的 TB 级性能突破
架构·kfs
KKKlucifer16 小时前
运营商全域安全运维支撑体系落地实践
运维·安全
老王以为16 小时前
走进 AI Agent
前端·人工智能·架构
SLD_Allen16 小时前
大模型推理引擎架构深度解析:vLLM、SGLang与TensorRT-LLM的技术路线对决
架构·vllm·sglang