feat(admin): add device credential isolation

This commit is contained in:
QiuSW
2026-08-04 20:57:33 +08:00
parent 41881e81f3
commit 66355a7f89
20 changed files with 1745 additions and 87 deletions
+24 -4
View File
@@ -16,8 +16,8 @@
│ · 提交围栏、结果调和、内部证据与审计 │
│ · 服务端渲染管理页面 │
└───────────────────┬─────────────────────┘
│ HTTPS / JSON
│ Bearer + 设备绑定
│ 本机回环 HTTP / JSON(MVP)
│ Bearer + 设备绑定;非回环前必须先上 TLS
v
┌─────────────────────────────────────────┐
│ 采购工具(client/,Python + PySide6) │
@@ -141,6 +141,8 @@ T-103 真机证据表明:拼多多 `8.17.0`、goods_id `937122477375` 通过
| 外部支付页 | 检测到外部支付交接立即停止,不读取、保存或输入凭据 | 凭据泄露 |
| 安全校验 | 验证码、风控、人脸、短信出现即停止,不绕过 | 封号 / 违规 |
| 内部截图 | 可上传页面已显示的地址/手机号;不解析成字段或日志,完整 XML 不上传 | 非必要扩散 |
| 身份隔离 | 管理 session+CSRF 与设备 Bearer 分属不同路由域,混合凭据不叠加权限 | 设备越权 / 会话冒充 |
| Bearer 传输 | MVP 仅绑定 IPv4 回环 `127.0.0.1:8080`;非回环访问先建立 HTTPS/TLS 终止 | 明文局域网泄露 token |
| 授权一次性 | 一条任务版本只有一份有效授权;幂等重放不生成第二份 | 重复采购 |
| 服务端提交围栏 | 点击前原子创建唯一提交记录;失败或响应不明不得点击 | 并发 / 断网重复下单 |
| App 版本绑定 | 运行版本不同于证据版本时停止并重新取证 | 旧判据误点 |
@@ -247,6 +249,18 @@ CREATE TABLE order_submissions (
UNIQUE (attempt_id)
);
-- 设备 token 明文只在签发完成后显示一次;数据库仅保存原始 32 字节 token 的 SHA-256
CREATE TABLE device_credentials (
device_id TEXT PRIMARY KEY, -- 规范小写 UUIDv4
display_name TEXT NOT NULL, -- 非秘密运维名称
token_sha256 BLOB NOT NULL UNIQUE, -- 恰好 32 字节
status TEXT NOT NULL, -- ACTIVE | REVOKED
created_at TEXT NOT NULL,
revoked_at TEXT,
CHECK ((status = 'ACTIVE' AND revoked_at IS NULL)
OR (status = 'REVOKED' AND revoked_at IS NOT NULL AND revoked_at >= created_at))
);
-- INTERNAL_RAW 原始截图;原文件名和客户端路径不进入数据库
CREATE TABLE evidence_assets (
id TEXT PRIMARY KEY,
@@ -273,6 +287,12 @@ MVP 不再用 `spec_trials` 作为审批记录,也不存在 `authorized_unit_p
`purchase_attempts` / `order_submissions` 的执行与审计事实;管理员授权的资金边界始终是
`total_price_cap`。
设备凭据由本机管理 CLI 签发、列出和撤销。token 是 32 字节加密随机值,以 64 位小写十六进制
只显示一次;服务端把 token 解码回原始字节后计算 SHA-256,并与按设备 id 查出的 32 字节 BLOB
恒定时间比较。未知 id 也执行固定宽度 dummy compare。认证逐请求查库,因此撤销事务提交后才开始的
请求全部拒绝;提交前已经完成认证的在途请求不追溯取消。格式/未知/错配/撤销统一空 401,存储故障
空 503,两类都在读取业务请求体前失败闭合。
### 5.2 状态机
```text
@@ -331,8 +351,8 @@ DRAFT / PENDING / NEEDS_MANUAL ─管理员取消(围栏前)→ CANCELED
- 单个 PNG 最大 10 MiB、单边最大 8192 px、总像素最大 16,777,216;同时验证 multipart MIME、
PNG 魔数、完整解码、字节数、尺寸和调用方声明的 SHA-256。
- 上传 handler 必须先通过设备认证,再解析 Content-Type 或读取 body。T-301 前生产认证器固定拒绝,
不创建临时 token,也不把管理员 session 当设备身份。
- 上传 handler 必须先通过逐请求 SQLite 设备认证并再次校验规范 principal,再解析 Content-Type 或
读取 body。空库、无效或已撤销凭据拒绝,认证存储故障返回 503;不把管理员 session 当设备身份。
- 文件写入显式配置的私有证据根目录:同目录随机临时文件 → 流式 hash → 校验 → `fsync` → 原子
rename 到 SHA-256 内容地址 → 最后事务写数据库。数据库永远不指向半文件或缺失文件。
- SQLite 与文件系统不能组成跨资源事务;极端故障最多留下不可达孤儿文件。不得为清理孤儿而删除
+23 -5
View File
@@ -16,11 +16,29 @@
| 身份 | 凭据 | 能力 |
| --- | --- | --- |
| 管理员 | `HttpOnly; Secure; SameSite=Lax` 会话 cookie + CSRF | 建单、开始采购、查看内部证据、人工调和 |
| 设备 | `Authorization: Bearer <device-token>` + 设备 id | 心跳、领取、事件、截图、围栏与结果 |
| 管理员 | `HttpOnly; SameSite=Lax` 会话 cookie + CSRF;HTTPS 部署设 `Secure=true` | 建单、开始采购、查看内部证据、人工调和 |
| 设备 | `Authorization: Bearer <device-token>` + `X-CMBuyer-Device-ID` | 心跳、领取、事件、截图、围栏与结果 |
| ERP(V2) | 独立凭据 | 只读来源同步,不访问采购结果 |
设备凭据不能建单或开始采购;管理会话不能调用设备接口。未认证统一返回 `401`,无权返回 `403`。
设备凭据不能建单或开始采购;管理会话不能调用设备接口。凭据缺失或无效返回 `401`,已认证但无权
或管理写请求缺少有效 CSRF 返回 `403`;认证存储故障按下述规则返回 `503`。
设备请求的认证头采用以下固定格式:
- `Authorization` 和 `X-CMBuyer-Device-ID` 必须各出现且只出现一次;代理合并出的逗号列表也拒绝。
- Authorization scheme 按 HTTP 规则大小写不敏感,但 scheme 后只允许一个 ASCII 空格;token 必须是
加密随机生成的 32 字节值对应的 64 位小写十六进制文本。
- 设备 id 必须是规范小写 UUIDv4。token 与设备 id 同时绑定,未知、错配、格式错误和已撤销均返回
空 `401`,可带 `WWW-Authenticate: Bearer`,不区分具体原因。
- 认证器逐请求读取 SQLite,不缓存 ACTIVE 结论。SQLite 查询或连接故障返回空 `503`;401 与 503
都必须发生在 Content-Type 解析和 body 读取之前。
- 管理 cookie 不替代设备凭据;Bearer 也不替代管理 session/CSRF。两类凭据同时出现时,各路由仍只
采用自己的身份域,不把权限相加。
设备 token 由本机管理 CLI 签发,只在签发事务提交后向操作者显示一次;SQLite 仅保存 token 原始
32 字节的 SHA-256(32 字节 BLOB),list/revoke、日志、错误和 HTTP 响应均不显示 token 或 hash。
撤销幂等且不会恢复旧 token。MVP 服务只绑定 IPv4 回环 `127.0.0.1:8080`,设备 Bearer 只经过本机回环 HTTP;
未来非回环访问必须先建立 HTTPS/TLS 终止与代理信任边界。
### 错误响应
@@ -220,8 +238,8 @@
- 同一设备主体和 `upload_key` 的同载荷重放返回原资产;任务、attempt、截图或元数据变化返回 `409`。
- 首次成功返回 `201`,幂等重放返回 `200`。响应只含资产 id、关联 id、kind/tier、hash、字节数、
MIME、宽高和采集时间,不含设备 token、原文件名或存储路径。
- T-301 接入真实设备 Bearer 身份前,生产 `DeviceAuthenticator` 固定拒绝全部上传;不得使用管理员
session、临时 token 或共享密钥代替设备身份。
- 生产上传使用逐请求 SQLite 设备认证;空凭据库、未知或已撤销设备均拒绝。不得使用管理员 session、
临时共享密钥或其他身份代替设备凭据。
### `POST /api/v1/purchase-attempts/{aid}/submission-fence`