LevaAverGit/pd-scanner-152fz-v2
GitHub: LevaAverGit/pd-scanner-152fz-v2
一款本地优先的隐私合规预筛查工具,通过爬取公共网站并分析个人数据收集信号,生成符合 152-FZ 标准的结构化证据报告。
Stars: 0 | Forks: 0
# PD Scanner
[](https://github.com/LevaAverGit/pd-scanner-152fz/actions/workflows/ci.yml)
一款隐私优先的本地 Web 应用程序,用于对公共网站进行隐私合规性预分析。
给定一个 URL,PD Scanner 会启动一个无头浏览器,对网站进行爬取,对
个人数据收集点进行分类,并生成符合
俄罗斯联邦法律 152-FZ《个人数据法》及一般数据保护原则的结构化证据报告。
所有分析均在本地运行。没有任何数据会离开您的设备。
## PD Scanner 的功能
- **爬取**公共网站最多 20 个页面(有界的同站 BFS)
- **检测**收集个人数据的表单字段,并将其分类为 12 个类别
- **观察**外部网络请求,并按供应商类型对第三方主机进行分类
- **检测**隐私链接、同意复选框、营销同意以及捆绑同意文本
- **解析**链接的政策页面 —— HTML、PDF 和 DOCX —— 以提取 8 个标准政策章节
- **推断**来自表单提交 URL、脚本和网络模式的下游数据处理者
- **构建**结构化的 152-FZ 证据层:风险等级、政策差距、人工验证目标
- **导出**完整的发现结果,格式为 JSON 和 Markdown 报告
- **截图**种子 URL 以作为视觉记录
## PD Scanner 不做的事
| 边界 | 原因 |
|---|---|
| 不向表单提交真实的个人数据 | 设计上的安全性 —— 仅在显式启用合成模式时使用合成的占位符值 |
| 不绕过 CAPTCHA | 绝不尝试任何 CAPTCHA 绕过行为 |
| 不绕过身份验证 | 需要登录的页面不在范围内 |
| 不跟踪外部链接 | 仅限同一主机;不进行跨域爬取 |
| 不宣称确定的法律合规性 | 所有发现均基于启发式的公共信号观察;法律结论需要专家分析 |
| 基于规则的启发式分类 | 无外部 LLM 或 AI API 依赖;所有分类都是确定性且可审计的 |
| 不远程存储数据 | SQLite 数据库仅在本地使用 |
| 不扫描私有 IP 范围 | SSRF 防护拦截所有私有/环回/链路本地地址 |
## 为什么“观察 / 推断 / 操作者提供”的区分很重要
PD Scanner 中的每一项发现都通过其认知状态进行了明确标记:
- **`observed`** —— 扫描器直接观察到的(例如,对 `analytics.google.com` 的网络请求,指向 HubSpot 的表单提交操作)
- **`inferred`** —— 基于带有声明置信度的公共信号推导出的(例如,带有 `portalId` 的隐藏表单字段暗示使用了 HubSpot,即使未得到确认)
- **`operator_supplied`** —— 由操作者提供的(例如,`integration_evidence.crm_destination = "Bitrix24"`)
这种区分很重要,因为:
1. 它使每一项声明的来源都透明且可审计
2. 它防止了在审计报告中将猜测与观察混为一谈
3. 它允许操作者提供的上下文丰富发现结果,而不会与扫描器的观察结果相混淆
## 技术栈
| 层级 | 技术 |
|---|---|
| 后端 | Python 3.11, FastAPI, Playwright (异步), aiosqlite |
| 数据模型 | Pydantic v2 |
| PDF 提取 | PyMuPDF (fitz) |
| DOCX 提取 | python-docx |
| 前端 | React 18, TypeScript, Vite, Tailwind CSS |
| 数据库 | SQLite(本地文件,自动迁移) |
| 测试 | pytest-asyncio, httpx ASGITransport |
## 工程亮点
- **异步 pipeline** —— FastAPI + Playwright + aiosqlite;扫描作为后台任务运行,因此 API 会立即返回;事件循环中没有阻塞式 I/O
- **有界 BFS 爬虫** —— 仅限同一主机,最多 20 个页面;SSRF 防护会解析主机名,并在任何外部请求之前拦截 RFC1918、环回和链路本地地址
- **分层分析** —— DOM 分类 -> 供应商检测 -> 同意信号 -> 政策解析 (HTML + PDF + DOCX) -> 152-FZ 证据综合;每一层均可独立测试
- **认知标记** —— 每一项发现都被标记为 `observed`、`inferred` 或 `operator_supplied`;来源在整个数据模型和报告中均得到保留
- **全面使用 Pydantic v2** —— 所有 API 输入/输出、数据库模型和服务间数据均通过 Pydantic 验证;没有字符串类型化 (stringly-typed) 的结果处理
- **303 个测试,0 警告** —— 通过 `httpx.ASGITransport` 进行进程内 HTTP 测试;通过 `tmp_path` + `patch` 实现单测数据库隔离;测试中没有真实的网络调用
## 后端架构
```
POST /api/scan
↓
URL Validation (SSRF guard, scheme check)
↓
Scan record persisted (status=pending) → HTTP 200 returned
↓ [background task]
Playwright BFS crawler (up to 20 pages, same-host)
│
├── DOM Parser → form fields, links
├── PD Classifier → DataCategoryItem list
├── Consent Detector → checkbox / bundled text / absent
├── Vendor Classifier → VendorSummaryItem list
└── Network Capture → third-party hosts
↓
Policy Parser (HTML/PDF/DOCX) → PolicyAnalysis
↓
Integration Audit → ProcessorMapItem list
↓
152-FZ Assessment → FZ152Assessment (risk level, gaps, targets)
↓
Screenshot + Report export (JSON + Markdown)
↓
Scan record updated (status=complete)
```
## API 与数据流
| 端点 | 方法 | 描述 |
|---|---|---|
| `/api/scan` | POST | 提交 URL;立即返回 `scan_id` |
| `/api/scan/{id}` | GET | 轮询结果;完成时返回完整的 `ScanResult` |
| `/api/scan/diff` | POST | 比较两个已完成的扫描 |
| `/api/history` | GET | 分页扫描历史 |
| `/api/history/{id}` | DELETE | 删除扫描记录和相关文件 |
| `/api/health` | GET | 存活检查 |
有关请求/响应示例和错误处理,请参见 `docs/API_OVERVIEW.md`。
有关按阶段划分的完整 pipeline 分解,请参见 `docs/DATA_FLOW.md`。
## 测试策略
```
make test # 303 backend tests
make type-check # TypeScript strict check
make verify # both + frontend build
```
所有后端测试均使用 `httpx.ASGITransport`(无真实服务器),并通过 `tmp_path` + `unittest.mock.patch` 实现隔离的 SQLite 数据库。那 2 个 Playwright 测试使用本地 fixture 服务器。
有关完整的测试策略和模式,请参见 `docs/QUALITY_ASSURANCE.md`。
## 为什么这个项目对开发者职位很重要
- **全栈实现**:异步 Python API + React TypeScript 前端 + SQLite
- **非平凡的后端 pipeline**:具有明确服务边界的多阶段异步处理
- **面向生产环境的设计决策**:SSRF 防护、仅本地存储、认知标记、
安全门控 —— 每一项都在 `docs/ARCHITECTURE_DECISIONS.md` 中附有详细的理由说明
- **测试纪律**:303 个测试、进程内 HTTP 测试、单测数据库隔离 —— 这是可测试的
架构,而不仅仅是覆盖率数字
- **领域理解**:将 152-FZ 的要求转化为可检测的信号,并带有
明确的局限性 —— 展示了确定范围并构建一个对其能力边界
诚实透明的工具的能力
## 前置条件
| 依赖 | 版本 | 安装 |
|---|---|---|
| Python | 3.11+ | `brew install python@3.11` 或 [python.org](https://www.python.org/) |
| Node.js | 18+ | `brew install node` 或 [nodejs.org](https://nodejs.org/) |
| make | 任意 | 预装于 macOS / Linux |
## 环境配置
```
# 创建 Python venv,安装所有 backend deps,
# 安装 Playwright Chromium,安装 frontend deps
make install
```
## 运行
**终端 1 —— 后端**(FastAPI 运行于 8000 端口):
```
make dev-backend
```
**终端 2 —— 前端**(Vite 开发服务器运行于 5173 端口):
```
make dev-frontend
```
在浏览器中打开 **http://localhost:5173**。
## 演示流程
1. 启动后端 + 前端
2. 打开 http://localhost:5173
3. 粘贴一个公共注册页面的 URL(例如某公司的 `/register` 或 `/signup` 页面)
4. 点击 **扫描**
5. 等待 10–30 秒让爬虫完成
6. 查看:检测到的数据类别、供应商观察结果、政策分析、152-FZ 风险评估
7. 从导出部分下载 JSON 或 Markdown 报告
## 测试
```
make test # backend tests (303 tests)
make type-check # frontend TypeScript strict check
make build # frontend production build
make verify # all three in sequence — full clean check
```
**当前状态:303 个测试,0 警告。**
覆盖范围:
- **API**:健康检查、扫描创建/获取、历史记录、删除、SSRF URL 验证(环回、RFC1918、链路本地地址均被拦截)
- **分类器**:12 个数据类别、置信度评分、误报防护
- **页面分类器**:注册相关性、页面类型检测
- **供应商分类**:分析、广告技术、CDN、支付、跟踪器模式
- **政策解析器**:章节检测、运营者名称/联系方式提取
- **合成提交**:安全门控(拦截 CAPTCHA、最大提交次数上限、敏感字段检测)
- **下游处理者推断**:运营者证据 schema、处理者映射构建
- **152-FZ 评估**:同意机制类型划分、风险评分、差距生成
- **文档解析**:PDF / DOCX 类型检测、文本提取、解析状态传播
## 配置
通过环境变量进行后端设置(全部可选,前缀为 `PD_`):
| 变量 | 默认值 | 描述 |
|---|---|---|
| `PD_DB_PATH` | `pd_scanner.db` | SQLite 数据库文件路径 |
| `PD_CORS_ORIGINS` | `["http://localhost:5173"]` | 允许的 CORS 源 |
| `PD_LOG_LEVEL` | `INFO` | 日志级别 |
| `PD_ALLOW_LOCAL_TEST_TARGETS` | `false` | 允许 localhost / 127.x —— **仅限本地 fixture 测试,切勿用于生产环境** |
## API 参考
| 方法 | 路径 | 描述 |
|---|---|---|
| `POST` | `/api/scan` | 提交 URL 进行扫描 |
| `GET` | `/api/scan/{scan_id}` | 轮询扫描结果 |
| `GET` | `/api/history` | 分页获取扫描历史(`limit`, `offset`) |
| `DELETE` | `/api/history/{scan_id}` | 删除扫描记录 |
| `GET` | `/api/health` | 存活检查 -> `{"status": "ok"}` |
`POST /api/scan` 请求体:
```
{
"url": "https://example.com/register",
"notes": "optional free-text notes",
"enable_synthetic_submission": false,
"integration_evidence": null,
"operator_metadata": null
}
```
`integration_evidence` 和 `operator_metadata` 接受操作者提供的上下文
(CRM 平台、webhook URL、法定名称、INN),这些内容在所有输出中都会被明确标记为
`operator_supplied` —— 绝不会与扫描器的观察结果混淆。
## 项目结构
```
pd-scanner-152fz/
├── backend/
│ ├── app/
│ │ ├── main.py FastAPI app factory
│ │ ├── api/routes_scan.py Scan endpoints
│ │ ├── api/routes_history.py History endpoints
│ │ ├── core/config.py pydantic-settings (PD_ prefix)
│ │ ├── models/schemas.py All Pydantic v2 models
│ │ ├── models/db.py aiosqlite + auto-migration
│ │ └── services/
│ │ ├── scanner_service.py Full pipeline orchestrator
│ │ ├── crawler_service.py Bounded BFS crawler
│ │ ├── classifier_service.py PD field classifier
│ │ ├── consent_detection_service.py Consent signals
│ │ ├── vendor_classification_service.py Vendor types
│ │ ├── policy_parser_service.py Policy page + doc routing
│ │ ├── document_extraction_service.py PDF / DOCX extraction
│ │ ├── synthetic_submission_service.py Controlled form submission
│ │ ├── integration_audit_service.py Processor inference
│ │ ├── fz152_assessment_service.py 152-FZ evidence builder
│ │ └── report_service.py JSON + Markdown export
│ ├── tests/ 303 tests, 0 warnings
│ └── requirements.txt
├── frontend/
│ ├── src/pages/DashboardPage.tsx
│ ├── src/pages/ScanDetailsPage.tsx
│ ├── src/components/ PolicyAnalysisPanel, FZ152AssessmentPanel, …
│ └── package.json
├── docs/
│ ├── ARCHITECTURE.md
│ ├── THREAT_MODEL.md
│ ├── PRD.md
│ ├── 152FZ_CHECKLIST.md
│ ├── EVIDENCE_MODEL.md
│ ├── PRIVACY_AUDIT_MAPPING.md
│ └── RISK_SCORING.md
├── sample_reports/
│ ├── example_report.md
│ └── example_result.json
├── pytest.ini
├── Makefile
└── README.md
```
## 文档
| 文档 | 描述 |
|---|---|
| [`docs/API_OVERVIEW.md`](docs/API_OVERVIEW.md) | 端点、请求/响应示例、异步扫描生命周期、错误处理 |
| [`docs/DATA_FLOW.md`](docs/DATA_FLOW.md) | 完整的按阶段 pipeline:URL -> 爬虫 -> 分类 -> 导出 |
| [`docs/EXTENDING_SCANNER.md`](docs/EXTENDING_SCANNER.md) | 如何添加 PD 类别、供应商签名、政策章节、测试 |
| [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) | 系统架构、组件分解、pipeline 图表 |
| [`docs/ARCHITECTURE_DECISIONS.md`](docs/ARCHITECTURE_DECISIONS.md) | 为什么架构要这样设计(异步、Playwright、SQLite、基于规则) |
| [`docs/THREAT_MODEL.md`](docs/THREAT_MODEL.md) | 信任边界、SSRF 防护、合成提交安全性 |
| [`docs/QUALITY_ASSURANCE.md`](docs/QUALITY_ASSURANCE.md) | 测试策略、模式、数据库隔离、人工验证清单 |
| [`docs/152FZ_CHECKLIST.md`](docs/152FZ_CHECKLIST.md) | 152-FZ 信号清单,包含条款映射和局限性 |
| [`docs/EVIDENCE_MODEL.md`](docs/EVIDENCE_MODEL.md) | 证据类型、置信度模型以及每项发现代表的具体含义 |
| [`docs/PRIVACY_AUDIT_MAPPING.md`](docs/PRIVACY_AUDIT_MAPPING.md) | 扫描器输出如何映射到结构化的隐私审计阶段 |
| [`docs/RISK_SCORING.md`](docs/RISK_SCORING.md) | 启发式风险评分:因素、权重、阈值 |
| [`docs/INTERVIEW_NOTES.md`](docs/INTERVIEW_NOTES.md) | 面试推介与问答,严格界定范围和局限性 |
| [`CONTRIBUTING.md`](CONTRIBUTING.md) | 环境设置、运行测试、添加类别/供应商/章节 |
## 用例
PD Scanner 自动化了 152-FZ 或 GDPR 预审计中公共网站证据收集的阶段。分析师可以在一分钟内扫描客户的注册流程,并收到一份结构化的报告,展示:收集了哪些数据、通过哪些表单收集、路由给了哪些第三方、采用了什么同意机制,以及对照了什么已发布的政策。
## 这如何映射到真实的隐私合规工作
152-FZ 下的隐私合规需要证据收集、差距分析和结构化报告。该工具针对网站公开可观察的层级自动化了证据收集阶段。
| 本工具 | 真实合规工作流 |
|---|---|
| 有界 BFS 爬虫 | 证据收集范围(范围内的 URL) |
| PD 类别分类器 | PD 清单 —— 收集了什么数据以及在哪里收集 |
| 同意信号检测 | 同意机制审查 |
| 政策章节标记 | 隐私政策充分性审查 |
| 供应商 / 处理者映射 | 第三方和处理者登记册(第 6(4) 条) |
| 152-FZ 差距列表 | 供法律审查的初步差距分析 |
| `manual_validation_targets` | 需要专家跟进的审计发现 |
| JSON / Markdown 导出 | 审计证据包 |
有关完整的分阶段分解,请参见 [`docs/PRIVACY_AUDIT_MAPPING.md`](docs/PRIVACY_AUDIT_MAPPING.md)。
## 本项目在安全岗位中展示的能力
- 实用的 152-FZ 知识:将第 6、9、12、14、18.1、21 条转化为
启发式检测逻辑
- 隐私设计原则:SSRF 防护、不提交真实数据、仅本地存储、
对发现结果进行认知标记
- 证据模型设计:在整个数据模型中始终保持“观察 vs 推断 vs 操作者提供”的区分
- 全栈实现:异步 Python pipeline + React TypeScript UI + SQLite
- 测试纪律:包含 303 个带有异步 fixture 的测试,通过 tmp_path + patch 实现数据库隔离,
全面覆盖所有检测层
- 结构化报告:为技术和非技术受众提供面向合规性的输出
## 已知局限性
- BFS 爬虫被限制在 20 个页面 / 深度为 2 —— 深层网站仅被部分覆盖
- 需要交互才能显示表单的 JavaScript 密集型 SPA 可能无法被完全捕获
- 分类器依赖于字段的 `name`、`id`、`label`、`placeholder`、`aria-label`;混淆的属性会降低准确性
- PDF / DOCX 政策解析需要文本层;纯图像扫描的 PDF 会返回 `unreadable` 状态
- 合成提交默认关闭;启用后,仅提交明确合成的占位符值,并带有严格的安全门控
- 没有限流、多用户支持或远程部署强化
- 不防御 DNS 重绑定(解析后的主机验证被推迟)
## 安全免责声明
PD Scanner 默认是一个**只读分析工具**:
- 仅分析您明确提供的 URL
- 绝不提交真实的个人数据
- 绝不绕过身份验证或 CAPTCHA
- 绝不跟踪指向外部域名的链接
- 所有输出均存储在您的本地机器上
- 仅适用于公开可访问的页面
当设置 `enable_synthetic_submission: true` 时,仅使用明确合成的占位符
值(例如 `test@example.invalid`),在包含 CAPTCHA /
支付 / 敏感字段的页面上提交会被拦截,并且仅捕获请求元数据(不包含请求体,不包含 cookies)。
## 法律 / 合规免责声明
本工具仅执行**启发式技术分析**。
- 它**不能**确定是否在法律上符合 152-FZ 或任何其他法规。
- 它**不能**取代法律、DPO 或专业的合规审计。
- 所有发现都是需要人工验证的潜在风险指标。
- 任何输出均不构成法律意见或监管合规的保证。
- 本工具仅供教育、作品集展示和协助预审计目的使用。
标签:152-FZ, 代码示例, 数据分析, 数据合规, 特征检测, 网络安全, 自动化报告, 逆向工具, 隐私保护