feat: add external asset security scanner

This commit is contained in:
Codex
2026-06-29 20:00:17 +08:00
parent ecd269b548
commit ce538a57c7
17 changed files with 596 additions and 37 deletions

View File

@@ -97,7 +97,7 @@ npm --workspace @tiku-saas/worker run exports:once
3. 调用 `PUT /api/tenant-content/assets` 登记资源台账。托管对象默认进入 `status=draft``uploadStatus=pending`
4. 调用 `POST /api/tenant-content/assets/confirm-upload`由后端读取对象元数据并比对大小、MIME、SHA-256。
5. 校验通过后,后端只会置为 `uploadStatus=verified``securityScanStatus=pending`,并继续保持 `status=draft`。即使请求里传 `publish=true`,也不会绕过安全扫描直接发布。
6. 运行 assets worker。worker 会先复检对象元数据,再执行内置 `metadata_rules` 安全扫描,检查 object key、MIME allowlist、文件大小、扩展名/MIME 是否匹配等规则。
6. 运行 assets worker。worker 会先复检对象元数据,再执行内置 `metadata_rules` 安全扫描,检查 object key、MIME allowlist、文件大小、扩展名/MIME 是否匹配等规则;生产可配置 `WORKER_ASSET_SECURITY_SCANNER=metadata_rules,http`,在内置规则通过后调用外部 HTTP 杀毒/内容安全服务
7. 扫描通过后,资源变为 `securityScanStatus=passed`,后台再调用 `PUT /api/tenant-content/assets``status=active` 发布。
8. 学生端只能下载或预览 `active + uploadStatus=verified + securityScanStatus=passed` 的托管对象资源;后台管理员下载/预览也执行同一安全扫描门禁。
9. 学生端和后台管理员下载/预览都会写入 `content_asset_access_events`,包含 `assetId/userId/accessType/result/expiresInSec/signatureMode/ip/userAgent` 等审计字段。
@@ -168,7 +168,48 @@ npm --workspace @tiku-saas/worker run exports:once
| `security_scan_provider` | 扫描来源,例如 `metadata_rules``trusted_export_worker` |
| `security_scan_summary` | 风险等级、问题码、扫描证据和失败原因 |
当前内置 provider 是 `metadata_rules`。它不是完整杀毒引擎,但能阻断明显危险或不合规对象:跨租户 key、非法 object key、超限文件、MIME allowlist 外文件、扩展名/MIME 不匹配,以及测试/运营标记的强制失败。商用生产仍需继续接入真实 AV/内容安全 provider并将结果写入同一张事件表。
内置 provider 是 `metadata_rules`。它不是完整杀毒引擎,但能阻断明显危险或不合规对象:跨租户 key、非法 object key、超限文件、MIME allowlist 外文件、扩展名/MIME 不匹配,以及测试/运营标记的强制失败。
生产环境支持可插拔 HTTP provider`WORKER_ASSET_SECURITY_SCANNER=metadata_rules,http`。worker 会在 metadata 规则通过后调用外部扫描服务,并把外部扫描结果和内置规则合并为一次最终结果写回 `content_assets`。如果外部服务返回失败、响应无效、超时或不可用,默认 `fail-open=false`,资源会被标记为 `securityScanStatus=failed` 并从 `active` 退回 `draft`。生产 readiness 会阻断没有外部 scanner、HTTP endpoint 非 HTTPS、token 弱或开启 fail-open 的配置。
HTTP scanner 请求由 worker 发起前端不会接触扫描服务地址、token 或对象存储密钥。请求示例:
```json
{
"assetId": "00000000-0000-0000-0000-000000000000",
"tenantId": "00000000-0000-0000-0000-000000000001",
"assetType": "pdf",
"storageProvider": "aliyun_oss",
"bucket": "tenant-assets",
"objectKey": "tenant-id/assets/file.pdf",
"fileName": "file.pdf",
"mimeType": "application/pdf",
"fileSizeBytes": 4096,
"checksumSha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"metadata": {
"observed": {},
"declared": {}
},
"requestedAt": "2026-06-29T00:00:00.000Z"
}
```
HTTP scanner 响应契约:
```json
{
"status": "passed",
"riskLevel": "none",
"issueCodes": [],
"provider": "clamav",
"details": {
"engine": "clamav",
"signature": ""
}
}
```
`status` 只允许 `passed``failed``riskLevel` 只允许 `none/low/medium/high/critical``details` 会经过脱敏后写入扫描事件,`secret/token/password/privateKey/apiKey/authorization` 等字段会被替换为 `[redacted]`
扫描事件表:
@@ -273,6 +314,11 @@ WORKER_ASSET_BATCH_SIZE=50
WORKER_ASSET_MIN_AGE_SECONDS=300
WORKER_ASSET_RECHECK_INTERVAL_SECONDS=86400
WORKER_ASSET_REQUEST_TIMEOUT_MS=10000
WORKER_ASSET_SECURITY_SCANNER=metadata_rules
WORKER_ASSET_SECURITY_SCAN_HTTP_ENDPOINT=
WORKER_ASSET_SECURITY_SCAN_HTTP_TOKEN=
WORKER_ASSET_SECURITY_SCAN_HTTP_TIMEOUT_MS=10000
WORKER_ASSET_SECURITY_SCAN_FAIL_OPEN=false
```
## 生产建议
@@ -282,7 +328,7 @@ WORKER_ASSET_REQUEST_TIMEOUT_MS=10000
- 图片、PDF、视频分别设置合理的 CORS只允许前端域名和小程序业务域名访问。
- 开启对象版本控制、生命周期、跨区域复制或定时备份,满足后续容灾要求。
- 视频资源已接入 SVIP/播放次数校验、短期签名和播放日志生产阶段继续补转码、动态水印、CDN 防盗链和播放统计。
- 大文件上传已经支持 API 即时确认worker 元数据复检;后续继续补杀毒、转码、水印CDN 刷新。
- 大文件上传已经支持 API 即时确认worker 元数据复检、内置规则扫描和外部 HTTP 扫描契约;生产阶段必须接入真实扫描服务 endpoint/token并继续补转码、水印CDN 刷新和生命周期策略
## 官方依据