release: nexus btpanel session fix and app-v2

This commit is contained in:
Codex Release Bot
2026-07-08 22:31:31 +08:00
commit 1933f0af6e
2457 changed files with 350105 additions and 0 deletions
+85
View File
@@ -0,0 +1,85 @@
# 开发与审计标准文档
> 本目录存放所有可复用的开发规范与审计标准,可直接发给 AI 使用。
> **切换 AI 时**:先读 [`docs/project/standards-transfer-package.md`](../docs/project/standards-transfer-package.md)(全量转接 + 严谨性契约)。
---
## 项目功能与接续(非本目录,但 AI 必读)
| 文档 | 路径 | 用途 |
|------|------|------|
| **标准转接包** | [`docs/project/standards-transfer-package.md`](../docs/project/standards-transfer-package.md) | **全量标准索引**;逐行审查与开发同等强制 |
| **AI 接续 SSOT** | [`docs/project/AI-HANDOFF-2026-05-23.md`](../docs/project/AI-HANDOFF-2026-05-23.md) | 待办、验证、端口模型、接续提示词 |
| **全站功能清单** | [`docs/project/nexus-full-site-features.md`](../docs/project/nexus-full-site-features.md) | 18 页 + API 能力 Markdown 版 |
| **开发验收标准** | [`docs/project/development-acceptance-standard.md`](../docs/project/development-acceptance-standard.md) | L0~L5 验收层级、命令、上线闸门 |
| **功能说明书 HTML** | [`docs/Nexus-功能说明书.html`](../docs/Nexus-功能说明书.html) | 面向人的全站说明(浏览器打开) |
---
## 文档清单(权威正文在本目录)
### 1. 系统开发标准(完整版)
**文件**: `system-development-standard.md`
**用途**: 全栈系统开发规范,约束代码生成、Code Review、架构评审
**涵盖**: 实现铁律、架构分层、后端编码、前端安全、数据库规范、测试、迁移、文档交付
### 2. 逐行审计完整规范(完整版)
**文件**: `line-walk-audit-standard-v2.md`
**用途**: AI 执行全量逐行代码审计的完整操作手册
**涵盖**: 8 步走读流程、规则表(PY/FE/SH/SQL 共 27 条)、Closure 格式、批次规划、§十四 执行提示词
### 3. 审计核心原则(速查卡)
**文件**: `audit-core-principles.md`
**用途**: 精炼的审计底线,每次任务开始前给 AI 的系统提示词前缀
**涵盖**: 审计本质、实现原则、DoD 逐项勾选、严重度表、修复纪律、10 大高频漏报
> `docs/project/` 下同名文件为副本;**冲突以本目录为准**。
> Cursor 规则 `audit-line-review.mdc` 另引用 `docs/project/line-walk-audit-standard.md`Nexus 批次 SSOT)。
---
## 使用方式
### 给其他 AI 做代码审计(与团队同等严谨)
```
完整转接(推荐):
→ 粘贴 docs/project/standards-transfer-package.md §8 完整提示词
→ 或按该文档档 C 全文阅读三件套
最小化(仅原则,仍须满足 DoD):
→ 粘贴 audit-core-principles.md 全文
→ + line-walk-audit-standard-v2.md §十四.1 执行提示词
→ 附上要审计的代码
```
### 给其他 AI 做代码生成
```
→ 粘贴 system-development-standard.md 全文作为规范约束
→ + .cursor/rules/perfect-implementation.mdc + nexus-*.mdc
```
### 两者同时使用
```
系统提示词:
[system-development-standard.md 全文]
---
[audit-core-principles.md 全文]
任务:
请按以上标准对 [目录/文件] 进行逐行走读审计,发现问题直接修复。
```
---
## 版本历史
| 版本 | 日期 | 变更 |
|------|------|------|
| standards-transfer-package | 2026-05-23 | 全量标准转接包,接续 AI 必读 |
| system-development-standard v1.0 | 2026-05-23 | 从 Nexus mdc 规则 + Phase 14 审计实践提炼 |
| line-walk-audit-standard v2.0 | 2026-05-23 | 整合 audit-line-review.mdc + PY-11~PY-20 新规则 |
| audit-core-principles v1.0 | 2026-05-23 | 从 v2.0 提炼的精简版 |
+122
View File
@@ -0,0 +1,122 @@
# 逐行审计核心原则
> **适用**:所有代码审计任务,AI 执行前必读
> **地位**:不可妥协的底线;具体流程见 `line-walk-audit-standard-v2.md`
---
## 一、审计本质
**逐行走读 ≠ 攻击面抽查。**
| 逐行走读 ✅ | 攻击面抽查 ❌(不算审计)|
|-----------|---------------------|
| 全文 Read 至 EOF | 只 grep 关键词 |
| 对每条命中做上下文分析 | 列 Top 15 不给行号 |
| 每条 SAFE 写明依据 | 笼统说「整体 CLEAN」|
| 单回复 ≤5 个文件 | 声称单回复完成整个目录 |
---
## 二、实现原则(不可妥协)
```
❌ 不允许绕过 — 发现问题必须根治,不得用静默/硬编码/注释掩盖
❌ 不允许降级 — 不得以「先用着」「以后再改」推迟安全修复
❌ 不允许留债 — 不得留 TODO / FIXME 延后处理,当场能修必须当场修
❌ 不允许无依据 — SAFE 结论必须写明为何 SAFE,空结论等于没做
❌ 不允许无行号 — 每条 FINDING 必须 `文件:行号`,无行号无效
✅ 无法完美修复时 — 停下告知用户,确认方案后再动;不擅自从权
```
---
## 三、走读质量底线(DoD
完成一个文件,必须同时满足:
```
[ ] 全文 Read 覆盖第 1 行 ~ 第 N 行(N = 总行数)
[ ] 规则命中数 H == Closure 表行数(含 SAFE 和 FINDING
[ ] 每条 FINDING:文件:行号 + 类型 + 严重度 + 修复建议
[ ] 每条 SAFE:写明依据(argv / 白名单 / 常量 / 校验函数)
[ ] 有 API/WebSocket 路由:必须有入口表(路径 + 鉴权方式)
[ ] 标注外部输入 → sink,或写明「无外部输入」
```
---
## 四、严重度与处置
| 级别 | 定义 | 处置 |
|------|------|------|
| **P0** | 未授权可利用:RCE / IDOR / 仓库密钥 | **阻塞,当场修复** |
| **P1** | 认证后利用 / 高影响缺陷:注入 / XSS / 关键 TypeError | 当前批次修复 |
| **P2** | 正确性 / 安全加固:float无保护 / tz混用 / 非原子 | 本迭代修复 |
| **P3** | 防御性改进 / 代码整洁 | 顺手修或记录 |
| **📋** | 已知设计取舍,明确接受 | 文档化理由,不修复 |
**代码待办 = 0 是合并前硬性要求。**
---
## 五、修复纪律
每次修复必须:
1. **当场写 changelog**`docs/changelog/YYYY-MM-DD-<主题>.md`
2. **更新 FINDING 矩阵** — 将状态改为 ✅,附 commit hash
3. **commit 推送** — 修复单独 commit,不与无关改动混提
禁止:把 changelog 只写进 commit message 代替文档。
---
## 六、KPI 约定
```
P0 漏报:-100 分
P1 漏报:-50 分
无行号 FINDING:不计入有效 FINDING
SAFE 无依据:该条 Closure 不计入完成
```
---
## 七、最常漏报的 10 个模式
```python
# ❌ 1. tz 混用
now(utc) - mysql_naive_datetime # TypeError
# ❌ 2. shlex+双引号混用
f'"{shlex.quote(path)}.pid"' # 单引号被当字面量
# ❌ 3. list ** 解包
{**d1, **some_list, "k": v} # TypeErrorgather 静默捕获)
# ❌ 4. 非原子 rate limit
redis.incr(k); redis.expire(k, t) # 崩溃 → key 无 TTL 永久存在
# ❌ 5. 替换嵌套
cmd.replace("$PASS", password) # password 含 $USER → 二次替换
# ❌ 6. DB 列宽
String(500) Fernet 加密 RSA 4096 私钥 # ~4300 chars → DataError
# ❌ 7. cron 除零
value % step # step=0 未判断 → ZeroDivisionError
# ❌ 8. 日志泄密
logger.info(f"Redis: {settings.REDIS_URL}") # URL 含密码
# ❌ 9. 命令已发再检测
shell.stdin.write(data); check_dangerous(cmd) # 顺序反了,命令已执行
# ❌ 10. gather 吞异常
await gather(*tasks, return_exceptions=True) # 未遍历结果检查 Exception
```
---
*单独使用:将本文件内容贴给 AI 作为系统提示词前缀,再附上具体走读任务。*
+367
View File
@@ -0,0 +1,367 @@
# 逐行代码走读审计完整规范 v2.0
> **版本**: 2.0 · 2026-05-23
> **适用**: Python 后端 / 前端 / Shell 任意项目,AI 辅助全量逐行审计
> **来源**: audit-line-review.mdc + perfect-implementation.mdc + Nexus Phase 14 实战提炼
---
## 核心原则(不可妥协)
**逐行走读 ≠ 攻击面抽查。** 未满足本规范 DoD 的文件不得标记「已审计」。
**完美实现原则**(来自 perfect-implementation.mdc):
- ❌ 不允许发现问题后绕过 — 必须根治
- ❌ 不允许静默吞错 — `except Exception: pass` 须证明合理
- ❌ 不允许留 TODO/FIXME — 当场能修必须当场修
- ❌ 不允许无行号 FINDING — 每条必须 `文件:行号`
- ✅ 无法完美修复时必须停下,告知用户,确认方案后再动
---
## 一、术语
| 术语 | 定义 |
|------|------|
| **走读** | 对单文件按行号顺序阅读全部源码,对命中规则做上下文分析 |
| **触达** | 仅 grep / 打开文件,未满足走读 DoD,**不算完成** |
| **规则命中** | §四 规则表在某行产生匹配,计入列表 H |
| **Closure** | 对一条命中的判定:`SAFE`(附依据)或 `FINDING`(附行号+理由) |
| **DoD** | 单文件完成定义,见 §六 |
| **ACCEPTED** | 已知设计取舍,明确接受并文档化,不修复 |
---
## 二、适用范围
| 包含 | 排除 |
|------|------|
| `server/**/*.py`(所有后端) | `docs/**`(仅做密钥扫描) |
| `web/app/*.{html,js}`(前端页面) | 第三方 minified JSvendor/ |
| `web/agent/**`(子机 Agent | 图片、字体、.pyc |
| `scripts/**``deploy/**``tests/**` | `frontend/node_modules/**` |
---
## 三、单文件走读流程(8 步,每步必做)
```
Step 1 登记:文件路径、语言、总行数 N(wc -l 或工具读取)
Step 2 全文 Read 至 EOF(每段 ≤200 行;末段 offset ≥ N-5,须覆盖末行)
Step 3 规则扫描 → 命中列表 H(§四 规则表)
Step 4 对 H 每条 Read ±15 行上下文 → Closure 表(SAFE 或 FINDING
Step 5 API / WebSocket 文件:写入口表(方法 / 路径 / 鉴权方式)
Step 6 标注外部输入 → sinkSQL / shell / 文件路径 / innerHTML / 出站 HTTP
Step 7 八类归类核对(§七)
Step 8 满足 DoD → 标「逐行完成 ✓」
```
**单会话上限**:最多 **5 个文件** 完成 8 步(防止上下文过长导致漏报)。
---
## 四、规则表
### 4.1 Python 后端
| ID | 匹配模式(示意) | 审查要点 |
|----|-----------------|----------|
| PY-01 | f-string + exec / shell / ssh / sysctl / curl / sudo / bash | 是否经 shell?整段 `key=value` 是否安全引用?`shlex.quote(f"{k}={v}")` vs 仅 quote value |
| PY-02 | `subprocess.` / `create_subprocess_shell` / `os.system` | `shell=True`?命令来源是否可控? |
| PY-03 | `exec_ssh_command` / `conn.run(` / `asyncssh` | 远程 shell 拼接?命令是否用户可控? |
| PY-04 | `text(` / `execute(` / `raw(` / 字符串拼 SQL | SQL 注入?是否用参数绑定? |
| PY-05 | `@router.` / `@app.` / `websocket` / `@app.post` | 是否有 JWT / API Key 鉴权?PUBLIC 白名单是否过宽? |
| PY-06 | 字面量 `API_KEY` / `SECRET_KEY` / `password =` / `token =` | 硬编码凭据? |
| PY-07 | `pickle` / `yaml.load(` / `eval(` / `exec(` | 危险反序列化 / 动态执行 |
| PY-08 | API Key `==` / `!=` | 应用 `secrets.compare_digest`(防时序攻击) |
| PY-09 | `except` 后直接 `pass` / `return None` / `logger.xxx` 无 re-raise | 是否静默吞掉安全异常?静默失败是否可接受? |
| PY-10 | `open(` + 路径变量 / `pathlib.Path(` + 用户输入 | 路径遍历?白名单校验? |
| PY-11 | `datetime.now()` 不带 `timezone.utc` / 与 `now(utc)` 相减 | tz-aware vs naive 混用 → TypeError |
| PY-12 | `float(x)` / `int(x)` 对外部数据无 try/except | 非数字输入 → ValueError → 500 |
| PY-13 | `{**d1, **some_list, ...}` dict 解包 | list 不是 mapping → TypeError(常被 gather 静默捕获)|
| PY-14 | `redis.incr(k)``redis.expire(k, t)` 两步分开 | 非原子:崩溃在中间 → key 无 TTL 永久存在 |
| PY-15 | `String(N)` 列存加密后数据 / 大型字段 | 加密后长度是否仍 ≤ NRSA 4096 Fernet 后 ~4300 chars |
| PY-16 | `command.replace("$VAR", value)` 多次循环替换 | 密码含 `$VAR` 模式 → 嵌套二次替换 |
| PY-17 | `shlex.quote(x)` 后再包进 `f'"{q}.suffix"'` | 单引号结果被双引号包裹 → 文件名含字面单引号 |
| PY-18 | cron field `step = int(part[2:])``value % step` | step=0 → ZeroDivisionError |
| PY-19 | `logger.info(f"... {settings.REDIS_URL}")` / `{settings.DATABASE_URL}` | 连接串含密码 → 日志泄露 |
| PY-20 | `send_telegram(f"...{e}...")` / `{result}` 等外部通知 | DB/Redis 详情泄露到 Telegram/Slack |
### 4.2 前端(HTML / JS
| ID | 匹配模式 | 审查要点 |
|----|---------|----------|
| FE-01 | `innerHTML` / `outerHTML` | 每个 `${}` 是否经 `esc()` / `escAttr()` |
| FE-02 | `localStorage.setItem` + token / key | JWT / API Key 存 localStorageCSP 是否有 |
| FE-03 | `WebSocket` + `?token=` / `?t=` in URL | Token 在 URL → 日志 / Referer 泄露 |
| FE-04 | 裸 `fetch(` 不经封装函数 | 是否绕过统一 `apiFetch`(含鉴权 / CSRF)? |
| FE-05 | `onclick="…${` / `href="javascript:${` | JS 属性注入(XSS |
| FE-06 | `<script src="https://cdn.` / `<link href="https://` | 是否有 SRIintegrity)? |
| FE-07 | `data-xxx="${` / 后端数据直插 HTML 属性 | 属性注入?须 `escAttr` |
### 4.3 Shell / Deploy / SQL / Config
| ID | 范围 | 要点 |
|----|------|------|
| SH-01 | `*.sh` | 变量引用是否加 `""`、curl 是否含密钥、路径是否变量拼接 |
| DP-01 | `nginx*.conf` | CSP 头、TLS 配置、是否暴露 `.env` |
| SQL-01 | `*.sql` | `GRANT ALL``'%'`(任意主机)、明文密码 |
| CFG-01 | `.env.example` / `config.json` | 是否含真实密钥? |
**H=0 规则**:即使无规则命中,仍须完成 Step 2 全文 Read,Closure 表写明「规则零命中,全文确认 CLEAN」。
---
## 五、Closure 表格式
```markdown
| 文件 | 行号 | 规则 | 判定 | 严重度 | 理由 / 修复建议 |
|------|------|------|------|--------|-----------------|
| sync_engine_v2.py | 243 | PY-01 | FINDING | P2 | config value 含换行符拼入 echo → 多行注入;剥离 \n\r\x00 |
| sync_engine_v2.py | 95 | PY-01 | SAFE | — | rsync 两端路径均 shlex.quote,不经用户可控拼接 |
| agent.py | 69 | PY-08 | SAFE | — | compare_digest(provided, stored) 已使用 |
| auth.py | 116 | PY-09 | ACCEPTED | — | /logout 无 JWT 是设计决策:access token 无状态,仅清 refresh |
```
- **SAFE**:必须写明依据(argv 不经 shell / Pydantic 校验 / 常量输入 / 白名单)
- **FINDING**:对应八类 + 行号 + 一行修复建议
- **ACCEPTED**:写明接受理由,不得留空
---
## 六、单文件 DoD(缺一不可)
```
[ ] Step 2Read 覆盖 1..N(报告写明 N 与实际 Read 范围)
[ ] Step 4len(H) == Closure 行数(含 SAFE
[ ] Step 5:有 @router / websocket → 有入口表;纯 __init__ 可 N/A
[ ] Step 6:已标注外部输入→sink 或写明「无外部输入」
[ ] Step 7:八类已勾选
[ ] 无「Top 15」「已大体审计」「整体 CLEAN」等笼统结论
```
---
## 七、八类分类与严重度
| 类 | 代号 | P0 例 | P1 例 | P2/P3 例 |
|----|------|-------|-------|---------|
| 安全漏洞 | VULN | 未授权 RCE / IDOR Shell | XSS / 命令注入 / 越权 | 信息泄露 / 会话固定 |
| 安全风险 | RISK | 仓库内生产 API Key | 明文 HTTP 传凭据 | 非原子 rate limit |
| 功能缺陷 | BUG | 加密列宽不足 → 截断 | tz-aware/naive 相减 → TypeError | float() 无保护 → 500 |
| 外部化缺失 | EXT | 无全局鉴权中间件 | 无危险命令拦截 | 缺日志 |
| 规范违规 | STYLE | — | 未 esc 的 innerHTML | 过宽 public 路径 |
| 性能 | PERF | — | 心跳每次写 MySQL | N+1 查询 |
| 可用性 | UX | — | 错误无提示静默失败 | 缺进度状态 |
| 优化点 | OPT | — | — | 重复逻辑 / 死代码 |
**KPI 约定**P0 漏报 -100P1 漏报 -50(用于 AI 质量评估)。
---
## 八、严重度定义
| 级别 | 定义 | 处置 |
|------|------|------|
| **P0** | 未授权可利用:RCE / IDOR / 仓库密钥 / 任意用户越权 | **阻塞合并,当场修复** |
| **P1** | 认证后可利用或高影响缺陷:命令注入 / XSS / 凭据泄露 / 关键路径 TypeError | 当前批次修复 |
| **P2** | 正确性或安全加固:float 无保护 / tz 混用 / 非原子操作 / 列宽不足 | 本迭代修复 |
| **P3** | 低风险 / 防御性改进 / 代码整洁 | 顺手修或记录 |
| **📋 ACCEPTED** | 设计取舍,已知且接受 | 文档化理由 |
| **🟡 PARTIAL** | 有缓解但未彻底 | 文档化缓解措施 |
---
## 九、批次规划建议
| 批次 | 目录 / 主题 | 文件数上限 |
|------|------------|-----------|
| 1a | 路由层(API handlers | 15 |
| 1b | 业务层(Services | 10 |
| 1c | 基础设施层(DB / Redis / SSH / 外部服务) | 20 |
| 1d | 入口与配置(main / config / background | 8 |
| 1e | 前端页面(HTML / JS | 15 |
| 1f | 子机 / 脚本 / 测试 | 20 |
| 2a | 修复后重读(Phase 1 修改行数最多的文件) | 10 |
| 2b | 遗留文件 / 新增文件补充走读 | 按需 |
每批结束产出:
1. **走读报告** `docs/reports/audit-phase-Xn-line-walk.md`
2. **FINDING 矩阵**更新(全量状态表)
3. **修复 commit** 推送
---
## 十、全量 FINDING 矩阵格式
维护一张总表:
```markdown
| ID | 文件:行号 | 类 | 级 | 状态 | 描述 | 修复/备注 |
|----|-----------|----|----|------|------|---------|
| F1a-01 | api/auth.py:45 | VULN | P0 | ✅ | ... | ... |
| F1b-03 | services/sync.py:187 | BUG | P2 | ⏳ | ... | ... |
| F1c-02 | infra/ssh.py:92 | RISK | P1 | 📋 | ... | 网络隔离接受 |
```
每次修复后更新状态。**代码待办 = 0 是合并前硬性要求。**
---
## 十一、文档纪律(来自 perfect-implementation.mdc
```
修复流程:发现 FINDING → 当场修复(P0/P1)或记录(P2/P3)→ 写 changelog → commit
```
每次有意义的修复必须:
| 文档 | 路径 | 必含内容 |
|------|------|---------|
| Changelog | `docs/changelog/YYYY-MM-DD-<主题>.md` | 日期 / 变更摘要 / 动机 / 涉及文件 / 验证方式 |
| 走读报告 | `docs/reports/audit-phase-Xn-line-walk.md` | 文件清单 / Closure 全表 / Findings / 走读签字 |
| FINDING 矩阵 | `docs/reports/audit-findings-matrix.md` | 全量状态,持续更新 |
**禁止**:把 changelog 只写在 commit message 里代替文档。
---
## 十二、禁止行为清单
```
❌ 无 `文件:行号` 的 FINDING
❌ `rg 命中 ≠ Closure 数` 仍标「完成」
❌ 子代理 Top 15 代替 Closure 全表
❌ 单回复声称整个 server/ 或 web/ 已完成
❌ 逐行审计与修代码混做(除非用户明确要求同步修复)
❌ SAFE 无依据(不写为何 SAFE,等于没做)
❌ FINDING 无修复建议(至少一行说明如何修)
❌ 发现问题留 TODO,不当场修复(来自 perfect-implementation.mdc
```
---
## 十三、常见高频漏报(必须覆盖)
| 陷阱 | 典型错误代码 | 正确做法 |
|------|-------------|---------|
| shlex 引号混用 | `f'"{shlex.quote(path)}.pid"'` | `f'{shlex.quote(path)}.pid'`(单引号直接用) |
| tz 混用 | `naive_dt - datetime.now(utc)` | 比较前统一:`naive.replace(tzinfo=None)` 或全用 utc |
| list `**` 解包 | `{**d, **some_list}` | `{"key": some_list}` |
| 非原子 rate limit | `incr(k); expire(k, t)` | `pipeline: incr + expire` 或 Lua 脚本 |
| DB 列宽 | `String(500)` 存加密 SSH 私钥 | `Text`RSA 4096 Fernet 后 ~4300 chars|
| 变量替换嵌套 | `cmd.replace("$PASS", pw)` + pw 含 `$USER` | 先全部替换为 sentinel,再用真值替换 sentinel |
| cron 除零 | `value % step` 未判 step==0 | `if step == 0: return False` |
| 日志泄密 | `logger.info(settings.DATABASE_URL)` | 脱敏后再 log(仅显示 host:port |
| 命令已执行再检测 | shell.stdin.write(cmd) → check_dangerous(cmd) | 顺序错误;WebSSH 层无法阻断,只能记录 |
| gather 吞异常 | `gather(*tasks, return_exceptions=True)` 未检查结果 | 遍历结果,对 isinstance(r, Exception) 单独处理 |
---
## 十四、给 AI 的执行提示词
### 14.1 开始走读(粘贴给 AI
```
请按以下规范对 [文件/目录] 进行逐行代码走读审计:
【规范】
1. 单文件 8 步:登记行数 → 全文 Read 至 EOF → 规则扫描 → Closure 表 → 入口表 → 输入→sink → 八类核对 → DoD
2. 每段 Read ≤200 行;末段须覆盖末行(offset ≥ N-5)
3. 规则命中列表 H:对每条 Read ±15 行上下文
4. Closure 表每行必须有:文件 | 行号 | 规则 | SAFE/FINDING | 严重度 | 理由
5. FINDING 格式:ID | 文件:行号 | 类型(VULN/BUG/RISK...) | P0/P1/P2/P3 | 描述 | 修复建议
6. H=0 仍须全文 Read,Closure 表注明「规则零命中,全文 CLEAN」
7. 单回复最多 5 个文件完成 8 步
【禁止】
- 无行号的 FINDING
- 仅 grep 不全文 Read 就标「已审计」
- 声称整个目录一次完成
【重点规则】(必检)
- PY-01: f-string 命令拼接
- PY-08: API Key == 比较(应用 compare_digest
- PY-09: except 静默
- PY-11: tz-aware/naive 混用
- PY-14: redis INCR+EXPIRE 非原子
- PY-15: String(N) 列宽不足
- FE-01: innerHTML 未 esc
【产出】
- Closure 全表(含 SAFE
- FINDING 列表(含行号)
- 入口表(API 文件)
- 走读签字:文件 | N | H 数 | FINDING 数
```
### 14.2 修复验证
```
以下 FINDING 已修复,请验证修复是否完整:
FINDING: [ID] [文件:行号] [描述]
修复前:
[原代码]
修复后:
[新代码]
请确认:
1. 修复是否覆盖了原始问题
2. 修复是否引入新问题
3. 是否有边界情况遗漏
4. 若确认正确,将状态更新为 ✅
```
### 14.3 闭环声明生成
```
请根据以下走读记录生成 Phase X 走读闭环声明:
覆盖批次:[Xa, Xb, ...]
FINDING 矩阵:[附表]
闭环声明必须包含:
1. 覆盖范围(批次 + 文件列表 + 大约行数)
2. FINDING 统计(总数 / ✅已修复 / 📋接受 / 🟡部分 / ⏳待修)
3. CLEAN 文件列表(附简要理由)
4. 代码待办数(目标 = 0
5. 遗留观察(不修复的设计说明)
6. 签字:Phase X 走读已闭环,可进入 [下一阶段]
```
---
## 十五、闭环声明模板
```markdown
# Phase X 走读闭环声明
**日期**: YYYY-MM-DD
**标准**: 本规范 v2.0
## 覆盖范围
| 批次 | 目录 | 文件数 | 总行数 |
...
## FINDING 统计
- 总计: N 条
- ✅ 已修复: N
- 📋 接受: N
- 🟡 部分缓解: N
- ⏳ 代码待办: **0**(合并前硬性要求)
## CLEAN 文件(无 FINDING
- `file1.py` — 鉴权正确,命令均 shlex.quote,无外部输入 sink
- ...
## 遗留观察(不修复)
- 子机 shell 执行面(F1f-01):📋 架构接受,网络隔离 + 审计日志
## 签字
Phase X 走读已闭环。代码待办 0。可进入 [首次部署 / 下一功能迭代]。
```
---
*本规范完全通用,可直接粘贴给任何 AI 执行逐行走读任务。路径、批次编号按实际项目调整。*
+499
View File
@@ -0,0 +1,499 @@
# 系统开发完整标准 v1.0
> **版本**: 1.0 · 2026-05-23
> **适用**: Python/FastAPI 后端 + 前端 全栈项目
> **来源**: perfect-implementation.mdc + nexus-*规则 + Phase 14 审计实践提炼
> **性质**: 强制规范,不得降级或妥协
---
## 零、实现铁律
```
❌ 不允许为了实现而实现 — 每行代码必须有明确目的
❌ 不允许降级实现 — 不以「先用着」为由降低安全/架构/质量
❌ 不允许妥协实现 — 发现问题必须根治,不得绕过
❌ 不允许留技术债 — TODO / FIXME / 临时方案禁止遗留
✅ 无法完美实现时必须停 — 告知用户确认方案后再换路,不擅自从权
```
---
## 一、开发执行顺序(每次必走)
```
需求确认 → 设计文档 → 技术文档 → 实现 → 测试验证 → changelog → 更新项目记忆文档
```
| 阶段 | 产出路径 | 必含内容 |
|------|---------|---------|
| 设计文档 | `docs/design/specs/YYYY-MM-DD-<topic>-design.md` | 背景与目标、方案对比、选定方案及理由、接口/数据模型、安全约束、验收标准 |
| 技术文档 | `docs/design/plans/YYYY-MM-DD-<topic>.md` | 涉及文件清单、实现步骤、依赖/配置变更、测试要点、回滚方式 |
| Changelog | `docs/changelog/YYYY-MM-DD-<主题>.md` | 日期、变更摘要、动机、涉及文件、是否需迁移/重启、验证方式 |
**新功能/架构变更/非平凡重构**:先设计文档,再写代码。
**改动时已有模块缺文档**:一并补全,不得只改代码不留档。
**禁止**:把 changelog 只写进 commit message。
---
## 二、架构分层(禁止跨层直连)
```
HTTP 路由层 → Service 层 → Repository 层 → Domain 模型
(api/) (services/) (*_repo.py) (models/)
基础设施层(database / redis / ssh / telegram)仅被 Service 层或 Repository 层调用
前端(web/app/)仅通过 HTTP API 与后端通信,不直接操作 DB/Redis
```
| 层 | 职责 | 禁止 |
|----|------|------|
| API 路由层 | 请求校验、鉴权 Depends、调用 Service、返回响应 | 写 SQL、直接操作 Redis |
| Service 层 | 业务编排、事务边界、审计写入 | 直接 import FastAPI 依赖 |
| Repository 层 | DB 读写、分页、批量操作 | 业务逻辑 |
| Domain 模型 | ORM 字段定义、关系 | 任何业务逻辑、FastAPI 依赖 |
| 基础设施层 | DB 连接池、Redis 客户端、SSH 池、外部 HTTP | 业务逻辑 |
---
## 三、后端编码规范(Python / FastAPI
### 3.1 API 路由
```python
# ✅ 正确:Pydantic 校验 + JWT 鉴权 + 结构化错误
@router.post("/resource", response_model=dict, status_code=201)
async def create_resource(
payload: ResourceCreate, # Pydantic 自动校验
admin: Admin = Depends(get_current_admin), # JWT 鉴权
service: ResourceService = Depends(...),
db: AsyncSession = Depends(get_db),
):
...
raise HTTPException(status_code=422, detail="具体原因") # 结构化错误
# ❌ 禁止:裸 except、无鉴权、无 Pydantic
@router.post("/resource")
async def create(data: dict): # 无 Pydantic,无鉴权
try:
...
except:
pass # 禁止静默吞错
```
### 3.2 鉴权矩阵(强制)
| 调用方 | 机制 | 备注 |
|--------|------|------|
| 管理端 API | `Depends(get_current_admin)` JWT Bearer | 所有业务路由必须 |
| Agent | `X-API-Key` header + `secrets.compare_digest` | 防时序攻击 |
| WebSocket | JWT query param `?token=` | ADR-011 |
| 安装向导 | 无 JWT | 仅安装模式且安装后 403 |
### 3.3 数据库
```python
# ✅ 时间戳:必须带 timezone
datetime.now(timezone.utc) # ✅
datetime.now() # ❌ naive,与 aware 比较 → TypeError
# ✅ 分页在 Repository 层
async def get_paginated(offset, limit) -> tuple[list, int]:
count = await session.execute(select(func.count(Model.id)))
data = await session.execute(select(Model).offset(offset).limit(limit))
# ✅ 字段长度:加密后的值须 Text 而非 String(N)
ssh_key_private = Column(Text) # RSA 4096 Fernet 后 ~4300 chars
encrypted_pw = Column(String(500)) # 短密码 Fernet 后 ~100 chars,可 String
# ✅ 外键删除策略:明确声明
ForeignKey("servers.id", ondelete="CASCADE") # 主记录删 → 关联删
ForeignKey("scripts.id", ondelete="SET NULL") # 主记录删 → 关联置 null
```
### 3.4 Redis
```python
# ✅ 使用 pipeline 保证 INCR + EXPIRE 原子性
pipe = redis.pipeline()
pipe.incr(key)
pipe.expire(key, window_seconds)
count, _ = await pipe.execute()
# ❌ 非原子(崩溃在中间 → key 无 TTL 永久存在)
count = await redis.incr(key)
await redis.expire(key, window_seconds) # 若崩溃 → 永久存在
```
### 3.5 SSH / Shell
```python
# ✅ 整段 key=value 必须整体 quote
cmd = f"echo {shlex.quote(f'{key}={value}')}" # ✅
cmd = f"echo {shlex.quote(key)}={shlex.quote(value)}" # ❌ 等号在引号外
# ✅ shlex.quote 结果不放进双引号(引号嵌套变字面量)
f"{shlex.quote(path)}.pid" # ✅ 产生 '/path/to/file'.pid
f'"{shlex.quote(path)}.pid"' # ❌ 产生 "'/path/to/file'.pid"(单引号字面量)
# ✅ 多行注入防御:剥离换行
safe_value = str(value).replace('\n',' ').replace('\r',' ').replace('\x00','')
```
### 3.6 凭据与加密
```python
# ✅ 存储:Fernet 加密
password_col = encrypt_value(plaintext)
# ✅ 响应:只返回布尔标记
{"password_set": bool(server.password)} # ✅
{"password": server.password} # ❌ 禁止返回密文或明文
# ✅ 变量替换:两轮替换防嵌套
SENTINELS = {"$DB_PASS": "\x00PASS\x00", "$DB_USER": "\x00USER\x00"}
VALUES = {"\x00PASS\x00": real_password, "\x00USER\x00": real_user}
for placeholder, sentinel in SENTINELS.items():
cmd = cmd.replace(placeholder, sentinel)
for sentinel, value in VALUES.items():
cmd = cmd.replace(sentinel, value)
# ✅ 日志:脱敏后记录
logger.info(f"Redis: {_mask_url(settings.REDIS_URL)}") # 非 settings.REDIS_URL 原文
```
### 3.7 错误处理
```python
# ✅ 向上抛,让调用方决定
def decrypt_value(ciphertext):
try:
return fernet.decrypt(...)
except Exception as e:
raise ValueError("解密失败") from e # ✅ 不返回密文,不静默
# ❌ 静默吞错(禁止)
except Exception:
return ciphertext # ❌ 返回密文 = 信息泄露
return None # ❌ 调用方看不到错误
# ✅ gather 异常必须检查
results = await asyncio.gather(*tasks, return_exceptions=True)
for r in results:
if isinstance(r, Exception):
logger.error("Task failed: %s", r)
```
### 3.8 Datetime 规范
```python
# ✅ 所有 datetime 带 timezone
from datetime import datetime, timezone
now = datetime.now(timezone.utc) # ✅
# ✅ MySQL 返回 naive,比较前统一
expires = record.expires_at # naiveMySQL 存储无时区)
now_cmp = datetime.now(timezone.utc)
if expires.tzinfo is None:
now_cmp = now_cmp.replace(tzinfo=None) # 统一为 naive 再比较
if expires < now_cmp: ...
# ✅ Cron 解析防除零
step = int(part[2:])
if step == 0:
return False # 无效 step,跳过
```
---
## 四、前端编码规范
### 4.1 API 调用
```javascript
// ✅ 统一封装函数(含 Authorization + 错误处理)
const data = await apiFetch("/api/resource/");
// ❌ 禁止裸 fetch(漏 Authorization
const data = await fetch("/api/resource/");
```
### 4.2 XSS 防御(强制)
```javascript
// ✅ 文本内容用 textContent 或 Alpine 绑定
el.textContent = serverName;
// ✅ 必须插入 HTML 时用 esc()
el.innerHTML = `<b>${esc(serverName)}</b>`;
// ❌ 禁止
el.innerHTML = `<b>${serverName}</b>`; // 未 esc
el.innerHTML = `<button onclick="fn('${val}')">`; // onclick 注入
// ✅ data 属性安全传值
`<button data-id="${esc(id)}" @click="handle($el.dataset.id)">`
```
### 4.3 静态资源
```html
<!-- ✅ 所有第三方 JS/CSS 自托管(/app/vendor/)或带 SRI -->
<script src="/app/vendor/alpinejs.min.js"></script>
<!-- ❌ 无 SRI 的 CDN -->
<script src="https://cdn.jsdelivr.net/npm/alpinejs@3"></script>
```
### 4.4 会话管理
- JWT 存 `localStorage`,到期前 2 分钟自动 refresh
- 401 响应:retry 一次,失败则 `_logout()` 跳登录页
- 8 小时无活动强制退出(与后端 `updated_at` 校验对齐)
---
## 五、安全规范
### 5.1 密钥管理
| 密钥 | 规则 |
|------|------|
| `SECRET_KEY` / `API_KEY` / `ENCRYPTION_KEY` | 只在 `.env`,不进 git,不可从 DB settings 覆盖,UI 不展示 |
| Agent `api_key` | `secrets.token_urlsafe(32)``compare_digest` 校验;启动时非空强制退出 |
| SSH 私钥 | Fernet 加密后存 `Text` 列;响应只返回 `ssh_key_private_set: bool` |
| JWT | `HS256`,含 `exp`/`sub`/`updated` claims;密码改后 `token_version` 递增使旧 token 失效 |
### 5.2 审计日志(所有 CUD 操作必须写)
```python
await audit_repo.create(AuditLog(
admin_username=admin.username,
action="create_server",
target_type="server",
target_id=created.id,
detail=f"name={created.name}",
ip_address=request.client.host if request.client else "",
))
```
### 5.3 外部通知脱敏
```python
# 发 Telegram / Slack / 邮件前必须脱敏
safe_msg = sanitize_external_message(str(exception)) # 去除 URL / password / key
await send_telegram_system_alert(safe_msg)
# 原始详情只写本地日志
logger.error("Full error: %s", exception, exc_info=True)
```
### 5.4 登录防暴破
- 失败计数(同 username + IP)→ 15 分钟锁定 → HTTP 429
- Refresh token 重用检测:旧 token 作废 + `token_version` 递增 + 审计日志
### 5.5 危险命令策略
- 中心管控面(server/api/):**阻断**危险命令,返回 400
- 子机 Agentweb/agent/):**记录**日志,架构上无法拦截(terminal 字符流)
---
## 六、数据模型规范
### 6.1 列类型选择
| 数据 | 类型 | 理由 |
|------|------|------|
| 短文本(<255 chars | `String(N)` | 索引高效 |
| 加密后的短密码 | `String(500)` | Fernet 加密后 ~100-300 chars |
| SSH 私钥(加密后) | `Text` | RSA 4096 Fernet 后 ~4300 chars |
| JSON 数组/对象 | `JSON``Text`(存 json.dumps | 视 DB 版本 |
| 命令输出 / 详情 | `Text` | 无长度限制 |
### 6.2 时间戳规范
```python
# ✅ 所有 DateTime 列用 aware callable(不用 onupdate=func.now()
created_at = Column(DateTime, default=lambda: datetime.now(timezone.utc))
updated_at = Column(DateTime, default=lambda: datetime.now(timezone.utc),
onupdate=lambda: datetime.now(timezone.utc))
```
### 6.3 外键删除策略
```python
ondelete="CASCADE" # 主记录删 → 关联记录也删(日志、会话)
ondelete="SET NULL" # 主记录删 → 关联置 null(软依赖)
```
### 6.4 索引规范
- 高频过滤字段必须加索引:`WHERE status = X AND created_at >= Y`
- 复合索引列顺序:高选择性列在前
- 迁移幂等:`CREATE INDEX IF NOT EXISTS` 或 try/except duplicate
---
## 七、测试规范
### 7.1 覆盖要求
| 新 endpoint | 测试路径 |
|------------|---------|
| 认证失败 | 无 token → 401 |
| 参数校验失败 | 必填字段缺失 → 422 |
| 成功路径 | 正常入参 → 正确响应 |
| 边界情况 | 空列表 / 极值 |
### 7.2 命名规范
```python
# tests/test_<模块名>.py
def test_<函数名>_<场景>_<预期结果>():
...
# 示例
def test_create_server_missing_domain_returns_422(): ...
def test_decrypt_value_fernet_fails_raises_valueerror(): ...
```
### 7.3 禁止
- 测试中硬编码生产密钥
- 跳过鉴权测试(`skip` 除非有充分理由)
- 测试依赖真实数据库(使用 mock 或 test fixture
---
## 八、数据库迁移规范
```python
# ✅ 幂等迁移(每次启动安全重跑)
try:
await conn.execute(text("ALTER TABLE t ADD COLUMN c VARCHAR(100)"))
except Exception as e:
if "duplicate" in str(e).lower() or "already exists" in str(e).lower():
pass # 列已存在,跳过
else:
raise # 真正的错误必须抛出
# ✅ 索引幂等
try:
await conn.execute(text("CREATE INDEX idx_name ON table (col)"))
except Exception as e:
if "duplicate" in str(e).lower():
pass
else:
raise
```
---
## 九、后台任务规范
### 9.1 多 Worker 防重复
```python
# ✅ Redis 分布式锁:只有 primary worker 运行后台任务
acquired = await redis.set(LOCK_KEY, worker_id, nx=True, ex=TTL)
if acquired:
task = asyncio.create_task(my_loop())
# ✅ 锁丢失时取消任务
if lock_lost:
for task in _background_tasks:
task.cancel()
```
### 9.2 后台任务结构
```python
async def my_background_loop():
logger.info("Loop started (interval=%ss)", INTERVAL)
while True:
await asyncio.sleep(INTERVAL)
try:
async with AsyncSessionLocal() as session:
await do_work(session)
except asyncio.CancelledError:
break
except Exception as e:
logger.error("Loop error: %s", e)
# 继续下一个周期,不终止整个 loop
```
### 9.3 Datetime 防区时
```python
# ✅ MySQL 返回 naive,统一处理
last_run = record.last_run_at # naive
now_naive = datetime.now(timezone.utc).replace(tzinfo=None)
if last_run.tzinfo is not None:
last_run = last_run.replace(tzinfo=None)
elapsed = (now_naive - last_run).total_seconds()
```
---
## 十、文档交付标准
### 每次 PR / 功能完成必须包含
```
[ ] docs/changelog/YYYY-MM-DD-<主题>.md 已创建
[ ] 涉及新 APIschemas.py 有 Pydantic 模型
[ ] 涉及新 DB 表/列:domain/models/__init__.py 已更新 + 迁移脚本幂等
[ ] 涉及安全变更:audit-log 写入已验证
[ ] 项目记忆文档(CLAUDE.md / AGENTS.md)已更新实施状态
```
### Changelog 必含字段
```markdown
# YYYY-MM-DD 主题
**日期**: YYYY-MM-DD
**动机**: 为什么要做这个变更
**变更摘要**: 做了什么
**涉及文件**: 列出主要文件
**是否需迁移**: 是/否(若是,说明步骤)
**验证方式**: 如何确认变更生效
```
---
## 十一、禁止代码示例
```python
# ❌ 技术债(禁止提交)
# TODO: fix later
result = "workaround"
# ❌ 静默失败
except Exception:
return None
# ❌ 无文档大改
# (新增整模块却无 docs/design 或 docs/changelog 记录)
# ❌ tz-aware/naive 混用
datetime.now() - record.created_at # 若一个 aware 一个 naive → TypeError
# ❌ SQL 注入
await session.execute(text(f"SELECT * FROM t WHERE id = {user_input}"))
# ❌ 命令注入
cmd = f"rsync {source} {target}" # source/target 未 shlex.quote
# ❌ 密钥返回 API
{"api_key": settings.API_KEY} # 禁止返回完整密钥
# ❌ 日志泄密
logger.info(f"DB: {settings.DATABASE_URL}") # URL 含密码
```
---
*本规范可直接发给 AI,用于约束代码生成、代码审查、架构评审任务。*