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 处问题
这个提交包含在:
2026-09-18 11:13:17 +08:00
父节点 3751dffef9
当前提交 f36149efc3
共修改 29 个文件,包含 1105 行新增和 558 行删除
+8 -34
查看文件
@@ -27,15 +27,11 @@
</div>
<p class="hint">
{% if s.cookie_hint %}当前 Cookie:{{ s.cookie_hint }}
{% if s.cookie_at %}({{ s.cookie_at }} 更新){% endif %}
—— 页面与接口都不回传明文,数据库里也是密文。{% endif %}
{% if s.cookie_at %}({{ s.cookie_at }} 更新){% endif %}。{% endif %}
<br>获取方式:Chrome 打开 <code>https://www.workbuddy.cn/profile/plans-usage</code> → F12 → Network →
任选一个 <code>billing</code> 请求 → 复制 Request Headers 里的 <code>cookie</code> 与 <code>user-agent</code>
(<b>两者必须取自同一次请求</b>),粘贴到下面。
<br><b>请粘贴你自己账号的 Cookie</b>:采集只使用本人凭证,各账号的数据互不可见。
{% if not is_admin %}
<br><b>这一块是你唯一可以修改的配置</b> —— 其余参数与调度时刻由管理员统一设定。
{% endif %}
任选一个 <code>billing</code> 请求 → 复制 Request Headers 的 <code>cookie</code> 与 <code>user-agent</code>
(两者须取自同一次请求)。
<br>采集只使用本人凭证,各账号数据互不可见。{% if not is_admin %}普通账号只有这一块可改。{% endif %}
</p>
<form id="formCred">
<label class="col">Cookie
@@ -92,11 +88,10 @@
</label>
<p class="hint">Cookie 就是账号凭证,关掉证书校验等于把它暴露给中间人,非必要不要关。</p>
<button class="btn primary" type="submit">保存采集参数</button>
<p class="hint">整日校验会按天重新拉云端 total 与本地比对,发现缺记录自动补入;设为 0 表示关闭(日常够用)。
这些是<b>实例级</b>参数,改一次对本机所有账号生效。</p>
<p class="hint">整日校验按天比对云端 total 与本地记录并补入缺失项,0 表示关闭。实例级参数,对本机所有账号生效。</p>
</form>
{% else %}
<p class="hint">这些参数影响采集行为与云端压力,属于整机策略,普通账号只读。</p>
<p class="hint">这些属于整机策略,普通账号只读。</p>
<table class="kv">
<tr><th>接口基址</th><td class="mono">{{ s.api_base }}</td></tr>
<tr><th>接口路径</th><td class="mono">{{ s.api_path }}</td></tr>
@@ -118,8 +113,7 @@
<label class="col">新密码<input name="new" type="password" autocomplete="new-password"></label>
<label class="col">确认新密码<input name="new2" type="password" autocomplete="new-password"></label>
<button class="btn primary" type="submit">修改密码</button>
<p class="hint">至少 {{ pwd_min }} 位,且需包含大写字母、小写字母、数字、符号中的至少两类。
修改成功后当前会话仍有效,不必重新登录。</p>
<p class="hint">至少 {{ pwd_min }} 位,需包含大写字母、小写字母、数字、符号中的至少两类。修改后当前会话仍有效。</p>
</form>
<hr class="sect-divider">
@@ -143,22 +137,6 @@
</section>
</div>
{% if not is_admin %}
<section class="card">
<div class="cardhead">
<h2>为什么采集参数是只读的</h2>
<span class="tag mute">普通账号</span>
</div>
<p class="hint" style="margin:0">
你只能维护<b>本人账号的凭证</b>(Cookie 与 User-Agent)—— 采集始终只用你自己的这份凭证,
拿回来的数据也只会存在你自己的作用域里,别人看不到、你也看不到别人的。
分页大小、超时、接口地址、TLS 校验、调度时刻这些属于<b>整机策略</b>:它们既关系到云端
压力,也关系到所有人的采集是否安全,因此统一由管理员设定。
<br>如果你确实需要调整,把上面任意一项截图给管理员即可。
</p>
</section>
{% endif %}
{% if is_admin %}
<section class="card">
<div class="cardhead">
@@ -191,11 +169,7 @@
value="{{ s.captcha_length }}">
<em class="unit">4~6 位</em></label>
<button class="btn primary" type="submit">保存实例设置</button>
<p class="hint">
验证码的答案只存在服务端 <code>captchas</code> 表里(下发到浏览器的只是一个随机 id),
一次性使用、5 分钟过期 —— 所以答案不会随会话 Cookie 泄漏出去。
关闭验证码会显著放大被撞库与批量注册的风险,只有在前面已经有可信网关时才考虑。
</p>
<p class="hint">验证码答案只存在服务端 <code>captchas</code> 表,一次性使用、5 分钟过期。关闭会显著放大撞库与批量注册的风险。</p>
</form>
</section>
{% endif %}