feat(安全): 对外暴露面加固 + 界面去 AI 化(v1.5.0)
界面(去 AI 味): - 大屏页清除 114 处生成器残留属性 data-page-node-id - 视觉系统改回工程控制台风格:去 radial/linear-gradient、去辉光、 去标题前彩色装饰条,改为中性灰阶 + 单一蓝色强调色;KPI 色条改状态点 - 精简各页说教式长提示;修掉 profile.html 泄漏到页面上的 Markdown 星号 - 删除登录页过时的「默认账号 admin / admin123」提示(1.4.0 起已无默认口令) 安全与隐私(按「将会被公网访问」收口): - 内部异常只回 8 位事件号,完整堆栈进服务端日志(web/api.py::_internal) - 导出文件名收敛:防响应头注入与路径穿越;manage.py passwd 补用户名校验 - 登录对不存在的账号也走一次哑哈希,抹平用户名枚举的时序差异 - /api/* 读接口限速 240 次 / 60 秒 / 账号(挡住循环调 /api/bundle) - 进程 umask 0077 + 目录 0700 / 文件 0600:对话正文与主密钥的落盘权限 - 表名与库文件路径只对管理员下发;大屏页所有数据插值转义 - --debug 只允许绑定回环地址;新增 Permissions-Policy 与 413 处理器 文档: - DEPLOYMENT 新增第十三节「安全与隐私基线」;迁移表补 1.4.0 → 1.5.0 行 - SECURITY 更新支持范围、新增「信息泄漏收敛」小节与上线检查项 - .codebuddy/ 加入 .gitignore(助手工作记忆不进仓库) 版本:1.4.0 → 1.5.0(无库结构变更,user_version 仍为 4) 验证:python tools/smoke.py → ok=264 fail=0;python tools/check_docs.py → 0 处问题
这个提交包含在:
+110
@@ -8,6 +8,116 @@
|
||||
|
||||
---
|
||||
|
||||
## [1.5.0] — 2026-09-18
|
||||
|
||||
**主题:按「将会被公网访问」重新审一遍界面与暴露面**
|
||||
|
||||
两件事:① 清掉界面里的 AI 生成残留与「AI 味」,把视觉从「霓虹渐变」改回工程控制台;
|
||||
② 按「能被公网访问」的前提逐条收口信息泄漏、注入与资源滥用。
|
||||
**没有库结构变更**(`user_version` 仍是 4),升级不需要手工介入。
|
||||
|
||||
### 安全与隐私加固(对外提供服务)
|
||||
|
||||
**主题:把「能被公网访问」当成前提,逐条收口信息泄漏与资源滥用**
|
||||
|
||||
这一轮没有新增功能,全部是收口。原则:**能把信息少给一点就少给一点,
|
||||
能把权限多收一层就多收一层**。
|
||||
|
||||
**信息泄漏(这类问题的共同点是:平时看不出问题,踩点阶段最好用)**
|
||||
|
||||
- **内部异常不再直出**。`/api/collect`、`/api/maintenance/*`、`/api/backups*` 的兜底
|
||||
`except Exception` 原本把 `str(e)` 原样回给客户端 —— 那里面会带绝对路径
|
||||
(`/app/workbuddy_portal/...`)、SQL 语句、`sqlite3` 的报错。现在统一走
|
||||
`web/api.py::_internal`:完整堆栈写进服务端日志并打一个 **8 位事件号**,
|
||||
客户端只拿到事件号。既不影响「出事了去查日志」,也不送材料。
|
||||
(`BadParam` / `Busy` / `NotReady` / `ApiError` / `BackupError` 这些**面向用户写的**
|
||||
业务异常不受影响,它们的文案本来就是给用户看的。)
|
||||
- **普通账号拿不到内部实现清单**。`/api/manifest`、`/api/bundle` 里的 `sources`
|
||||
(表名 `usage_records`、`daily 聚合视图` …)与 `archive`(`data/usage.sqlite`)
|
||||
只对管理员下发;非管理员的大屏页「数据源」卡片自动收起,避免留一个只有表头的空壳。
|
||||
- **响应头注入**。导出文件名里含用户名,而用户名**并不总是**注册正则的产物
|
||||
(`manage.py passwd` 建号时不校验、老库升级上来的名字也可能带引号或 CR/LF)。
|
||||
一旦名字里有引号或换行,直接拼 `Content-Disposition` 就是响应拆分。
|
||||
新增 `security.safe_filename()` 与 `security.content_disposition()`:
|
||||
收敛成 ASCII 安全名 + 按 RFC 5987 附上原名,`/records/export` 与
|
||||
`/profile/export` 都改走它。
|
||||
- **路径穿越**。`collect.export_csv()` 的账号名 tag 直接拼进文件路径 ——
|
||||
一个叫 `..\..\x` 的账号能把 CSV 写到数据目录之外。现在只保留 `[A-Za-z0-9._-]`,
|
||||
且结果固定落在 `EXPORT_DIR` 之下。同时 `manage.py passwd` **建号时补上用户名校验**
|
||||
(与注册页同一套),从根上不再产生这类名字。
|
||||
- **用户名枚举的时序侧信道**。`login_ok` 在账号不存在时不执行任何哈希计算,
|
||||
比「口令错误」快一到两个数量级,一个秒表就能枚举出哪些用户名真实存在。
|
||||
现在对不存在的账号也走一次**同代价的哑哈希**,两条路径耗时对齐。
|
||||
|
||||
**隐私:落盘权限(这条是对外部署最容易被忽略的)**
|
||||
|
||||
- 数据目录里躺着的是:`usage_records.prompt`(**用户与 AI 的完整对话正文**)、
|
||||
凭证密文、`instance.json`(主密钥)、`exports/*.csv`(对话正文的**明文**副本)、
|
||||
`backups/*.zip`(全库 + 主密钥)。默认 `umask 022` 会把它们留成 `0644 / 0755`,
|
||||
也就是**同机任何用户都能读**。
|
||||
- 新增 `config.harden_process()`:在进程启动时把 `umask` 收到 `0077`,
|
||||
此后新建的一切文件(含 SQLite 的 `-wal` / `-shm`)天然是 owner-only。
|
||||
刻意选「一条设置管全部」而不是逐个 `chmod` —— 逐点 `chmod` 一定会漏,
|
||||
而漏掉的那个文件里往往正好是最新的对话正文。
|
||||
- 配套 `harden_dir()` / `harden_file()` 收紧**已经存在**的目录(老版本留下的 0755)
|
||||
以及导出 CSV、备份 zip 落盘后的权限。Windows 无此模型,函数直接跳过。
|
||||
|
||||
**资源滥用**
|
||||
|
||||
- **读接口限速**:登录有 IP 锁定、注册有配额、采集有最小间隔,但读接口原本**没有任何刹车** ——
|
||||
一个注册账号把 F12 里的 `/api/bundle` 请求放进 `for` 循环就能持续吃 CPU 与出口带宽
|
||||
(那个接口要算全量逐日聚合,还会下发最多 2 万条明细)。现在 `/api/*` 按
|
||||
「账号(未登录时按来源 IP)」限速 240 次 / 60 秒,超出返回 `429`。
|
||||
阈值给得宽松:大屏切一次筛选只发 1~2 个请求,正常用户碰不到。
|
||||
|
||||
**其它**
|
||||
|
||||
- **`--debug` 不再能绑到对外地址**。Werkzeug 的交互式调试器等于任意代码执行,
|
||||
而 `--host` 默认就是 `0.0.0.0`;`python manage.py serve --debug` 会直接把 RCE 挂上公网。
|
||||
现在绑非回环地址直接拒绝启动,并提示本机调试的正确写法。
|
||||
- 新增 `Permissions-Policy` 响应头(显式关掉地理位置 / 麦克风 / 摄像头 / 支付 / USB)。
|
||||
- 新增 `413` 处理器:请求体超限时接口返回 JSON 而不是 Flask 默认 HTML 页
|
||||
(前端 `r.json()` 在 HTML 上会炸,表现成「Unexpected token <」)。
|
||||
- 文档:新增 [部署指南第十三节「安全与隐私基线」](DEPLOYMENT.md#十三安全与隐私基线) ——
|
||||
代码层已做到的、公网暴露前必须自己做的、存了哪些个人数据、保留期怎么定、上线自检命令。
|
||||
|
||||
验证:`python tools/smoke.py` **ok=264 / fail=0**(第 9 节专门覆盖上述各项:
|
||||
`safe_filename` 边界、真实导出响应头不含引号/换行、每个 `except Exception` 都走 `_internal`、
|
||||
读接口限速、安全响应头、大屏页已转义、普通账号拿不到数据源清单)。
|
||||
|
||||
### 界面去 AI 化
|
||||
|
||||
**主题:清掉界面里的 AI 生成残留与「AI 味」**
|
||||
|
||||
- **大屏页清理生成器残留**:`static/dashboard/index.html` 里 **114 处**
|
||||
`data-page-node-id="…"`(页面生成工具给每个节点打的标记)全部删除,
|
||||
文件从 64 KB 降到 58 KB。这类属性没有任何运行时作用,只是生成痕迹。
|
||||
- **视觉系统重做**(`static/css/app.css` + 大屏内联样式):由「深色底 + 霓虹渐变 + 辉光」
|
||||
改为中性的工程控制台风格 —— 灰阶打底、单一蓝色强调色。具体删掉了:
|
||||
`radial-gradient` 页面背景、按钮/激活态/分页/徽标的 `linear-gradient`、
|
||||
`box-shadow` 发光(品牌点、主按钮、日历格)、渐变文字(错误页大号状态码)、
|
||||
以及标题前的彩色装饰条(`.pagehead h1::before`、`.card h2::before`、`h1::before`)。
|
||||
- **KPI 改用灰度分层**:原来每张卡一条彩虹色左色条,现在 `--c` 只渲染成一个 8px
|
||||
状态点(`采集健康`/`自动备份` 这类状态卡仍能一眼区分),其余靠灰度与字重。
|
||||
- **图表配色同步降饱和**:ECharts `PALETTE`、日历 5 级色阶、热力图 `visualMap`
|
||||
与后台共用同一套语义色;数据系列改用蓝色阶 + 1 个琥珀色对比项。
|
||||
- **文案精简**:删掉说教式长段落(`配置管理` 页整张「为什么采集参数是只读的」说明卡)、
|
||||
把只给结论即可的提示压成一句话;`任务管理`/`配置管理`/`备份管理`/`用户管理`
|
||||
的长提示同步收敛。
|
||||
- **修掉两个真实缺陷**(都在这次清理中暴露):
|
||||
- `templates/profile.html` 的 Markdown 星号漏进 HTML —— `**任何时候都不回传明文**`
|
||||
会在页面上原样渲染成星号;
|
||||
- 登录页写着「首次部署默认账号 `admin` / `admin123`」,而 1.4.0 起已改用随机口令,
|
||||
该提示会把人引向一个永远登不上的口令,已删除。
|
||||
- **文档同步**:`docs/USER-GUIDE.md` 中引用上述被删文案的两处说明一并更新。
|
||||
界面截图 `docs/images/*.png` 仍是旧观感,需要用 `python tools/shots.py` 重出。
|
||||
|
||||
验证:`python tools/smoke.py` 全绿(ok=233 / fail=0),其中
|
||||
「页面 class 与 app.css 选择器对账」确认新版样式表没有漏掉任何模板在用的类名;
|
||||
`python tools/check_docs.py` 0 处问题。
|
||||
|
||||
---
|
||||
|
||||
## [1.4.0] — 2026-09-16
|
||||
|
||||
**主题:备份管理 · 对外提供服务的安全加固 · 资源与频率限制**
|
||||
|
||||
在新工单中引用
屏蔽一个用户