antoinevastel/fpscanner
GitHub: antoinevastel/fpscanner
一个轻量级的自托管浏览器指纹库,用于检测自动化框架和机器人流量。
Stars: 691 | Forks: 74
# 指纹扫描器
[](https://github.com/antoinevastel/fpscanner/actions/workflows/ci.yml)
## FPScanner:描述
一个轻量级的浏览器指纹库,用于机器人检测。
抓取已成为主流。人工智能和大型语言模型驱动的公司现在以以前只有专业参与者才能达到的规模爬取网络,通常不会明确遵守 `robots.txt` 或速率限制。与此同时,欺诈者不再需要完全依赖 OpenBullet 等公开框架或通用自动化堆栈。借助大型语言模型,编写针对特定网站的定制机器人变得更加简单、快速和便宜。
结果是一个更加广泛和多样的机器人生态系统:
- 更多的参与者抓取内容、训练模型或提取数据
- 更多的定制自动化,更难用过时的启发式方法进行指纹识别
- 在注册、登录和敏感工作流程中更多的滥用,而不仅仅是简单的抓取
在防御方方面,情况受到更多限制。
通常有两个选择:
- 非常基础的开源库,专注于简单或过时的信号
- 昂贵的黑盒机器人和欺诈解决方案,需要通过第三方 CDN 或供应商路由流量
并非每个网站都能负担得起企业级的机器人管理产品。即使成本不是主要问题,您可能也不想通过 CDN 路由所有流量或将所有检测逻辑外包给第三方。
这个库的存在就是为了填补这一空白。
它是一个**自托管、轻量级且最新**的浏览器指纹识别和机器人检测库,在设计时考虑了现实世界的约束。目标不是承诺完美的检测,而是为您提供反映机器人当前实际行为的坚实构建块。
这包括在玩具实现中经常被忽略的实际考虑:
- 防重放保护(时间戳 + nonce)
- 有效载荷加密以防止简单的伪造
- 可选混淆以增加逆向工程的成本
- 专注于强大的、低噪声的信号,而不是脆弱的技巧
这个库的设计和权衡直接借鉴了实际的生产经验以及这些文章中讨论的思想:
- [Roll your own bot detection: fingerprinting (JavaScript)](https://blog.castle.io/roll-your-own-bot-detection-fingerprinting-javascript-part-1/)
- [Roll your own bot detection: server-side detection](https://blog.castle.io/roll-your-own-bot-detection-server-side-detection-part-2/)
这些文章不是这个库的文档,但它们反映了相同的理念:了解攻击者实际做什么,接受没有单一信号是完美的,并构建您完全控制的简单、可组合的原语。
### 开源、生产就绪
这个库是开源的,但它对开源的含义并不天真。
在机器人检测中,开放是一把双刃剑。发布检测逻辑使攻击者更容易研究他们如何被检测。同时,防御者 routinely 研究开放和封闭的自动化框架、反检测浏览器和机器人工具,以发现新的信号和弱点。无论这个库是否开源,这种不对称性已经存在于生态系统中。
这里的目标不是依赖隐蔽性。而是承认攻击者会阅读代码,但仍要使滥用在运营上变得昂贵。
这就是为什么库将透明性与务实的强化相结合:
- **防重放机制**确保有效的指纹不能简单地被捕获一次然后大规模重用
- **构建时密钥注入**意味着攻击者无法在无法访问您的特定构建的情况下轻易生成有效的加密有效载荷
- **可选混淆**增加了逆向工程的成本,并使自动有效载荷伪造更加困难,而无需在真实浏览器中执行代码
这些控制并不意味着完美或不可破解。它们的目的是移除简单的捷径。攻击者不应该能够查看仓库,重新实现序列化器,然后开始从无头脚本发送令人信服的指纹。
更重要的是,检测不会在单个布尔标志处停止。
即使攻击者专注于绕过单个机器人检测检查,生成**完全一致的指纹**也更加困难。指纹编码信号、上下文和环境之间的关系。在会话、IP 和账户之间保持这种一致性需要真正的执行、仔细的状态管理和稳定的工具。
在实践中,这会在服务器端产生优势:
- 指纹可以随时间跟踪
- 重用模式和漂移变得可见
- 当攻击者部分模拟环境或错误地轮换工具时,不一致就会显现
这就是指纹在生产系统中的使用方式:不是作为一次性判决,而是作为观察规模上的结构、重用和异常的方式。
开源不会削弱这种方法。它使权衡变得明确。攻击者被认为是有能力和适应性的,而不是粗心的。库的设计相应地:强制真正的执行,限制重放,并在信号中保留足够的结构,以便一旦您随时间观察它,自动化就会留下痕迹。
## 功能
| 功能 | 描述 |
|---------|-------------|
| **快速机器人检测** | 客户端检测强大的自动化信号,如 `navigator.webdriver`、CDP 使用、Playwright 标记和其他常见自动化痕迹 |
| **浏览器指纹识别** | 短生命周期指纹,设计用于攻击检测、聚类和会话关联,而非长期设备跟踪 |
| **加密有效载荷** | 可选的有效载荷加密以防止简单伪造,加密密钥在构建时注入 |
| **混淆** | 可选的代码混淆以增加逆向工程的成本,使在不实际执行收集代码的情况下更难伪造有效指纹 |
| **跨上下文验证** | 检测不同 JavaScript 执行上下文(主页面、iframe 和 web worker)之间的不一致 |
## 快速开始
### 安装
```
npm install fpscanner
```
### 基本用法
```
import FingerprintScanner from 'fpscanner';
const scanner = new FingerprintScanner();
const payload = await scanner.collectFingerprint();
// Send payload to your server
fetch('/api/fingerprint', {
method: 'POST',
body: JSON.stringify({ fingerprint: payload }),
headers: { 'Content-Type': 'application/json' }
});
```
### 服务器端(Node.js)
```
// Decrypt and validate the fingerprint
// Use the same key you provided when building: npx fpscanner build --key=your-key
const key = 'your-secret-key'; // Your custom key
function decryptFingerprint(ciphertext, key) {
const encrypted = Buffer.from(ciphertext, 'base64');
const keyBytes = Buffer.from(key, 'utf8');
const decrypted = Buffer.alloc(encrypted.length);
for (let i = 0; i < encrypted.length; i++) {
decrypted[i] = encrypted[i] ^ keyBytes[i % keyBytes.length];
}
return JSON.parse(decrypted.toString('utf8'));
}
app.post('/api/fingerprint', (req, res) => {
const fingerprint = decryptFingerprint(req.body.fingerprint, key);
// Check bot detection
if (fingerprint.fastBotDetection) {
console.log('🤖 Bot detected!', fingerprint.fastBotDetectionDetails);
return res.status(403).json({ error: 'Bot detected' });
}
// Validate timestamp (prevent replay attacks)
const ageMs = Date.now() - fingerprint.time;
if (ageMs > 60000) { // 60 seconds
return res.status(400).json({ error: 'Fingerprint expired' });
}
// Use fingerprint.fsid for session correlation
console.log('Fingerprint ID:', fingerprint.fsid);
res.json({ ok: true });
});
```
就是这样!对于大多数用例,这就是您需要的一切。
## API 参考
### collectFingerprint(options?)
收集浏览器信号并返回指纹。
```
const scanner = new FingerprintScanner();
// Default: returns encrypted base64 string
const encrypted = await scanner.collectFingerprint();
// Explicit encryption
const encrypted = await scanner.collectFingerprint({ encrypt: true });
// Raw object (no library encryption)
const fingerprint = await scanner.collectFingerprint({ encrypt: false });
```
| 选项 | 类型 | 默认值 | 描述 |
|--------|------|---------|-------------|
| `encrypt` | `boolean` | `true` | 是否加密有效载荷 |
| `skipWorker` | `boolean` | `false` | 跳过 Web Worker 信号(如果 CSP 阻止 blob: URL 则使用) |
### 指纹对象
当解密后(或使用 `encrypt: false`),指纹包含:
```
interface Fingerprint {
// Bot detection
fastBotDetection: boolean; // true if any bot signal detected
fastBotDetectionDetails: {
hasWebdriver: boolean; // navigator.webdriver === true
hasWebdriverWritable: boolean; // webdriver property is writable
hasSeleniumProperty: boolean; // Selenium-specific properties
hasCDP: boolean; // Chrome DevTools Protocol signals
hasPlaywright: boolean; // Playwright-specific signals
hasWebdriverIframe: boolean; // webdriver in iframe context
hasWebdriverWorker: boolean; // webdriver in worker context
// ... more detection flags
};
// Fingerprint
fsid: string; // JA4-inspired fingerprint ID
signals: { /* raw signal data */ };
// Anti-replay
time: number; // Unix timestamp (ms)
nonce: string; // Random value for replay detection
}
```
## 它检测什么
该库专注于来自主要自动化框架的**强大、可靠的信号**:
| 检测 | 信号 | 框架 |
|-----------|--------|------------|
| `hasWebdriver` | `navigator.webdriver === true` | Selenium, Puppeteer, Playwright |
| `hasWebdriverWritable` | webdriver 属性描述符 | Puppeteer, Playwright |
| `hasSeleniumProperty` | `document.$cdc_`, `$wdc_` | Selenium WebDriver |
| `hasCDP` | CDP 运行时标记 | Chrome DevTools Protocol |
| `hasPlaywright` | `__playwright`, `__pw_*` | Playwright |
| `hasMissingChromeObject` | 缺少 `window.chrome` | Headless Chrome |
| `headlessChromeScreenResolution` | 800x600 默认值 | Headless 浏览器 |
| `hasHighCPUCount` | 不现实的核心数 | VM/容器环境 |
| `hasImpossibleDeviceMemory` | 不现实的内存值 | 伪造环境 |
### 跨上下文验证
机器人经常无法在执行上下文之间保持一致性:
| 检测 | 描述 |
|-----------|-------------|
| `hasWebdriverIframe` | 在 iframe 中检测到 webdriver 但主页面中没有 |
| `hasWebdriverWorker` | 在 web worker 中检测到 webdriver |
| `hasMismatchPlatformIframe` | 主页面和 iframe 之间的平台不同 |
| `hasMismatchPlatformWorker` | 主页面和 worker 之间的平台不同 |
| `hasMismatchWebGLInWorker` | worker 中的 WebGL 渲染器不同 |
## 指纹 ID (fsid) 格式
`fsid` 是一个受 JA4 启发的、保持局部性的指纹标识符。与简单的哈希不同,它被结构化为语义部分,使其既易于人类阅读又可用于部分匹配。
### 格式
```
FS1________
```
### 示例
```
FS1_00000100000000_10010h3f2a_1728x1117c14m08b01011h4e7a9f_f1101011001e00000000p1100h2c8b1e_0h9d3f7a_1h6a2e4c_en4tEurope-Paris_hab12_0000h3e9f
```
### 部分分解
| # | 部分 | 格式 | 示例 | 描述 |
|---|---------|--------|---------|-------------|
| 1 | **版本** | `FS1` | `FS1` | 指纹扫描器版本 1 |
| 2 | **检测** | n 位位掩码(FS1 中为 21 位) | `000001000000000000000` | 所有 fastBotDetectionDetails 布尔值(可扩展) |
| 3 | **自动化** | `<5位>h<哈希>` | `10010h3f2a` | 自动化布尔值 + 哈希 |
| 4 | **设备** | `<宽>x<高>cmb<5位>h<哈希>` | `1728x1117c14m08b01011h4e7a9f` | 屏幕、cpu、内存、设备布尔值 + 哈希 |
| 5 | **浏览器** | `f<10位>e<8位>p<4位>h<哈希>` | `f1101011001e00000000p1100h28b1e` | 功能 + 扩展 + 插件位掩码 + 哈希 |
| 6 | **图形** | `<1位>h<哈希>` | `0h9d3f7a` | hasModifiedCanvas + 哈希 |
| 7 | **编解码器** | `<1位>h<哈希>` | `1h6a2e4c` | hasMediaSource + 哈希 |
| 8 | **区域设置** | `<语言><数量>t<时区>_h<哈希>` | `en4tEurope-Paris_hab12` | 语言代码 + 数量 + 时区 + 哈希 |
| 9 | **上下文** | `<4位>h<哈希>` | `0000h3e9f` | 不匹配 + webdriver 标志 + 哈希 |
### 为什么是这个格式?
受 [JA4+](https://github.com/FoxIO-LLC/ja4) 启发,此格式支持:
1. **部分匹配** — 跨指纹比较特定部分(相同的 GPU 但不同的屏幕?)
2. **人类可读性** — `1728x1117c14m08` = 1728×1117 屏幕、14 核、8GB 内存
3. **可扩展性** — 添加新的布尔检查会追加一位,而不会破坏现有位置
4. **相似性检测** — 来自同一框架的机器人通常共享自动化/浏览器哈希
## 服务器端解密
该库使用简单的 XOR 密码和 Base64 编码。这在任何语言中都很容易实现。
### Node.js
```
function decryptFingerprint(ciphertext, key) {
const encrypted = Buffer.from(ciphertext, 'base64');
const keyBytes = Buffer.from(key, 'utf8');
const decrypted = Buffer.alloc(encrypted.length);
for (let i = 0; i < encrypted.length; i++) {
decrypted[i] = encrypted[i] ^ keyBytes[i % keyBytes.length];
}
let fingerprint = JSON.parse(decrypted.toString('utf8'));
// Handle double-JSON-encoding if present
if (typeof fingerprint === 'string') {
fingerprint = JSON.parse(fingerprint);
}
return fingerprint;
}
```
### Python
```
import base64
import json
def decrypt_fingerprint(ciphertext: str, key: str) -> dict:
encrypted = base64.b64decode(ciphertext)
key_bytes = key.encode('utf-8')
decrypted = bytearray(len(encrypted))
for i in range(len(encrypted)):
decrypted[i] = encrypted[i] ^ key_bytes[i % len(key_bytes)]
fingerprint = json.loads(decrypted.decode('utf-8'))
# Handle double-JSON-encoding if present
if isinstance(fingerprint, str):
fingerprint = json.loads(fingerprint)
return fingerprint
```
### 其他语言
该算法很容易移植:
1. **Base64 解码**密文以获取原始字节
2. **XOR 每个字节**与相应的密钥字节(循环使用密钥)
3. **解码**结果为 UTF-8 以获取 JSON 字符串
4. **解析**JSON 以获取指纹对象
请参阅 [`examples/`](./examples/) 文件夹获取完整的 Node.js 和 Python 服务器示例。
## 高级:自定义构建
默认情况下,fpscanner 使用一个占位符密钥,该密钥在您运行构建命令时被替换。对于生产环境,您应该使用自己的加密密钥并启用混淆,以使攻击者更难伪造有效载荷。
### 使用您自己的加密/混淆
该库提供内置加密和混淆,但**您不必使用它们**。如果您愿意:
- 使用 `collectFingerprint({ encrypt: false })` 获取原始指纹对象
- 在发送到服务器之前应用您自己的加密、签名或编码
- 在您的捆绑包上运行您自己的混淆工具(Terser、JavaScript Obfuscator 等)
推荐的方法是使用**某种形式的**加密 + 混淆——无论是库的内置解决方案还是您自己的。关键是要阻止攻击者在不执行实际收集代码的情况下轻松伪造有效载荷。
### 为什么需要自定义构建?
| 威胁 | 无保护 | 加密 + 混淆 |
|--------|---------------------|-------------------|
| 有效载荷伪造 | 攻击者可以制作假指纹 | 密钥隐藏在混淆代码中 |
| 重放攻击 | 攻击者捕获并重放指纹 | 服务器验证时间戳 + nonce |
| 代码检查 | 检测逻辑可读 | 控制流混淆使分析更加困难 |
### 使用您的密钥构建
```
npx fpscanner build --key=your-secret-key-here
```
这将:
1. 创建原始文件的备份(用于幂等重建)
2. 将您的加密密钥注入库中
3. 混淆输出以保护密钥
4. 覆盖 `node_modules/fpscanner/dist/` 中的文件
### 密钥注入方法
CLI 支持多种方法(按优先级顺序):
```
# 1. 命令行参数(最高优先级)
npx fpscanner build --key=your-secret-key
# 2. 环境变量
export FINGERPRINT_KEY=your-secret-key
npx fpscanner build
# 3. .env 文件
echo "FINGERPRINT_KEY=your-secret-key" >> .env
npx fpscanner build
# 4. 自定义 env 文件
npx fpscanner build --env-file=.env.production
```
### CI/CD 集成
添加 `postinstall` 脚本以使用您的密钥自动构建:
```
{
"scripts": {
"postinstall": "fpscanner build"
}
}
```
然后在您的 CI/CD 中将 `FINGERPRINT_KEY` 设置为秘密:
**GitHub Actions:**
```
env:
FINGERPRINT_KEY: ${{ secrets.FINGERPRINT_KEY }}
steps:
- run: npm install # postinstall runs fpscanner build automatically
```
### 构建选项
| 选项 | 描述 |
|--------|-------------|
| `--key=KEY` | 加密密钥(最高优先级) |
| `--env-file=FILE` | 从自定义环境文件加载密钥 |
| `--no-obfuscate` | 跳过混淆(更快,用于开发) |
#### 跳过混淆
混淆默认启用。为了在开发过程中更快地构建:
```
# 通过 CLI 标志
npx fpscanner build --key=dev-key --no-obfuscate
# 通过环境变量
FINGERPRINT_OBFUSCATE=false npx fpscanner build
# 在 .env 文件中
FINGERPRINT_OBFUSCATE=false
```
## 开发
### 本地开发脚本
```
# 快速构建(默认密钥,无混淆)
npm run build
# 使用 dev-key 构建,无混淆
npm run build:dev
# 构建并在 localhost:3000 提供 test/dev-source.html
npm run dev
# 使用混淆构建
npm run build:obfuscate
# 生产构建(密钥来自 .env,带混淆)
npm run build:prod
# 监视模式(更改时重新构建)
npm run watch
```
### 测试
```
npm test
```
## 安全最佳实践
1. **使用强随机密钥并定期轮换**
使用高熵密钥(至少 32 个随机字符)并定期轮换。因为加密密钥在 JavaScript 捆绑包中客户端传输,长期密钥为攻击者提供更多时间来提取和重用它们。轮换密钥迫使攻击者重新分析和重新适应,并需要重建和重新部署指纹脚本。
2. **在生产环境中使用混淆**
启用库的内置混淆或对最终捆绑包应用您自己的混淆步骤。没有混淆,加密密钥以明文形式出现在客户端代码中,使攻击者可以在不执行指纹逻辑的情况下轻松伪造有效载荷。混淆增加了密钥提取和有效载荷伪造的成本。
3. **在服务器端验证时间戳**
拒绝超过合理阈值的指纹(例如,60 秒)。这限制了捕获的有效载荷的实用性并减少了重放攻击的影响。
4. **跟踪 nonce**
可选择存储最近看到的 nonce 并拒绝重复。这提供了额外的重放保护层,特别是对于高价值或易受滥用的端点。
5. **随时间监控指纹分布**
不要将指纹视为一次性决策。监控指纹随时间的演变和分布。突然激增、新的主导指纹或异常的重用模式可能表明自动化或恶意活动,即使个别请求没有触发明确的机器人检测标志。
6. **敏感端点的深度防御**
在保护敏感流程(注册、登录、密码重置、API 访问)时,将此库与其他控制结合使用,如基于指纹的速率限制、行为分析、一次性邮箱检测以及验证码或风险认证等挑战机制。指纹作为更广泛的检测和缓解策略中的一层效果最好。
## 故障排除
### "未找到加密密钥!"
通过支持的方法之一提供密钥:
```
npx fpscanner build --key=your-key
# 或
export FINGERPRINT_KEY=your-key && npx fpscanner build
# 或
echo "FINGERPRINT_KEY=your-key" >> .env && npx fpscanner build
```
### 解密返回垃圾
确保您在服务器上使用的密钥与构建时使用的密钥**完全相同**。密钥必须完全匹配。
### 混淆很慢
在开发期间使用 `--no-obfuscate`。仅在生产构建中启用混淆。
### 在 watch 模式下构建失败并显示"堆内存不足"
如果您重复运行构建命令(例如,在 watch 任务中),现在已修复。构建脚本会自动备份和恢复原始文件,以防止重新混淆已经混淆的代码。如果您仍然遇到此问题:
1. 更新到最新版本的 fpscanner
2. 在开发/watch 模式下使用 `--no-obfuscate`(推荐)
3. 仅在生产构建中启用混淆
### `postinstall` 在 CI 中失败
确保在 `npm install` 运行之前将 `FINGERPRINT_KEY` 设置为环境变量。
## 限制和非目标
这个库提供构建块,而不是完整的机器人或欺诈检测系统。在生产环境中使用它之前,了解它的限制很重要。
### 开源和攻击者适应
该库是开源的,这意味着攻击者可以检查代码并调整他们的工具。这是预期的,反映了生态系统已经运作的方式。防御者 routinely 分析自动化框架和反检测浏览器,攻击者也对检测逻辑做同样的事情。
目标不是隐蔽,而是通过强制真正的执行、限制重放和保持难以大规模伪造的一致性约束来使滥用在运营上变得昂贵。
### 混淆不是灵丹妙药
可选的混淆依赖于开源混淆器,一些攻击者为其维护反混淆工具。混淆是一种摩擦机制,而不是保证。它减缓了分析并阻止了低投入的滥用,但有动机的攻击者仍然可以逆向工程代码。
### 客户端检测的局限性
所有客户端指纹和机器人检测技术都可以被伪造或模拟。该库专注于强大的、低噪声的信号,但没有单个信号或指纹应该被视为决定性的。
指纹是表示,而不是判决。它们的价值来自于观察它们随时间的行为、出现的频率以及它们与操作、 和账户的相关性。
### 不是端到端解决方案
现实世界的机器人和欺诈检测需要服务器端上下文、可观察性和迭代:监控流量、构建和测试规则以及随时间适应的能力。该库故意不提供仪表板、规则引擎或托管缓解。
如果您需要具有可观察性和持续维护的生产级端到端解决方案,请考虑使用专用平台如 [Castle](https://castle.io/)。
## 许可证
MIT
位掩码参考
#### 检测位掩码(FS1 中为 21 位,可扩展) ``` Bit 0: headlessChromeScreenResolution Bit 1: hasWebdriver Bit 2: hasWebdriverWritable Bit 3: hasSeleniumProperty Bit 4: hasCDP Bit 5: hasPlaywright Bit 6: hasImpossibleDeviceMemory Bit 7: hasHighCPUCount Bit 8: hasMissingChromeObject Bit 9: hasWebdriverIframe Bit 10: hasWebdriverWorker Bit 11: hasMismatchWebGLInWorker Bit 12: hasMismatchPlatformIframe Bit 13: hasMismatchPlatformWorker Bit 14: hasSwiftshaderRenderer Bit 15: hasUTCTimezone Bit 16: hasMismatchLanguages Bit 17: hasInconsistentEtsl Bit 18: hasBotUserAgent Bit 19: hasGPUMismatch Bit 20: hasPlatformMismatch ``` #### 自动化位掩码(5 位) ``` Bit 0: webdriver Bit 1: webdriverWritable Bit 2: selenium Bit 3: cdp Bit 4: playwright ``` #### 设备位掩码(5 位) ``` Bit 0: hasMultipleDisplays Bit 1: prefersReducedMotion Bit 2: prefersReducedTransparency Bit 3: hover Bit 4: anyHover ``` #### 浏览器功能位掩码(FS1 中为 28 位,可扩展) ``` Bit 0: chrome Bit 1: brave Bit 2: applePaySupport Bit 3: opera Bit 4: serial Bit 5: attachShadow Bit 6: caches Bit 7: webAssembly Bit 8: buffer Bit 9: showModalDialog Bit 10: safari Bit 11: webkitPrefixedFunction Bit 12: mozPrefixedFunction Bit 13: usb Bit 14: browserCapture Bit 15: paymentRequestUpdateEvent Bit 16: pressureObserver Bit 17: audioSession Bit 18: selectAudioOutput Bit 19: barcodeDetector Bit 20: battery Bit 21: devicePosture Bit 22: documentPictureInPicture Bit 23: eyeDropper Bit 24: editContext Bit 25: fencedFrame Bit 26: sanitizer Bit 27: otpCredential ``` #### 浏览器扩展位掩码(8 位) ``` Bit 0: grammarly Bit 1: metamask Bit 2: couponBirds Bit 3: deepL Bit 4: monicaAI Bit 5: siderAI Bit 6: requestly Bit 7: veepn ``` #### 插件位掩码(4 位) ``` Bit 0: isValidPluginArray Bit 1: pluginConsistency1 Bit 2: pluginOverflow Bit 3: hasToSource ``` #### 上下文位掩码(4 位) ``` Bit 0: iframe mismatch Bit 1: worker mismatch Bit 2: iframe.webdriver Bit 3: webWorker.webdriver ```标签:Bot检测, C2日志可视化, CIDR扫描, MITM代理, Web安全, 前端安全, 反爬虫, 反自动化, 安全防护, 恶意行为检测, 指纹识别, 数据可视化, 暗色界面, 机器人检测, 欺诈检测, 浏览器指纹, 用户代理识别, 网络安全, 网络爬取检测, 自动化攻击, 自托管, 蓝队分析, 设备指纹, 隐私保护