ADR-006 · 公开仓库脱敏策略 · 4 层防护 · 准备 GitHub repo 公开化
| 字段 | 值 |
|---|---|
| 状态 | accepted · 2026-05-28 |
| 日期 | 2026-05-28 |
| 决策者 | CTO + 项目主理 |
| 承接 | ADR-001 (multi-anchor) · ADR-005 (hybrid deployment) |
| 相关 | scripts/redact_check.py · scripts/check_public_safe.py · .gitignore · .github/workflows/check-public-safe.yml |
1. Context
1.1 · 起源 · 2 轮脱敏的累积代价
ADR-005 hybrid Phase 1 落地后, 项目主理 问 "检查 github 仓库是不是经过脱敏". 全仓审计 (107 处硬规则命中, 50 文件) 暴露了 4 个层次的脱敏 paradox:
| 层次 | 命中文件类型 | 是否必要含真名 |
|---|---|---|
| 公网 (CF Pages serve) | www/* | 不允许 (已清, 24/24 PASS) |
| 仓库 HEAD (collaborator 可见) | README/CLAUDE/Makefile/skills/templates | 不必要, 历史残留 |
| 仓库 history | 当时被 git tracked 的 docs/internal/* + 早期 docs/* | 是 (反映项目演进) |
| 脱敏机制本身 | scripts/redact_check.py + tests/fixture | 必须 (拦真名要先写真名) |
1.2 · 用户决策
询问"4 选 1":
- A · PRIVATE 不动: 0 工作量
- B · PRIVATE + 顺手清 C-cat: ~30 min
- C · 准备变 PUBLIC · 完整脱敏: ~2-3h ← user 选
- D · 暂不决定
并选了 docs/internal/ 处置 = A (gitignore 移出 git, 仅本地保留), git history = Y (用 git filter-repo 重写 history).
2. Decision
采用 "4 层防护" 公开仓库脱敏架构:
┌─ Layer 1 · 公网部署面 (www/) ────────────────────────────┐
│ CF Pages serve · check_public_safe preflight + GHA CI │
│ invariant: 0 硬规则命中 (真名/内部 URL) │
└──────────────────────────────────────────────────────────┘
┌─ Layer 2 · 仓库 HEAD (collaborator + 未来 public) ───────┐
│ README/CLAUDE/Makefile/skills/templates/configs │
│ invariant: 0 硬规则命中, 软规则 placeholder 化 (.example) │
└──────────────────────────────────────────────────────────┘
┌─ Layer 3 · 仓库 history (`git log -p` 可见) ─────────────┐
│ git filter-repo 重写, docs/internal + 早期 docs/* 全清 │
│ invariant: history 任何 commit 不含 docs/internal/ 路径 │
└──────────────────────────────────────────────────────────┘
┌─ Layer 4 · 脱敏机制本身 (justified self-reference) ──────┐
│ redact_check.py + tests/fixture + feishu_sync_filter.py │
│ invariant: 真名以 SENSITIVE_PATTERNS 形态存在, 不是数据 │
└──────────────────────────────────────────────────────────┘3. Consequences
3.1 · 优势
| 维度 | 改造前 | 改造后 |
|---|---|---|
| HEAD 真名 hits | 107 | 0 (除 justified self-reference) |
| docs/internal/ tracked | 是 (12 文件 143 hits) | 否 (gitignore + git rm --cached, 本地保留) |
| config/wiki_aliases.yaml tracked | 是 (含 anchor real-name 别名) | 否 (gitignored, ship .yaml.example placeholder 版) |
| config/feishu_sync_filter_rules.yaml.example | 含真名 trigger | placeholder 化 (<NAME_VARIANT_*>) |
| git history 含 docs/internal | 是 (54 commits) | 否 (filter-repo 重写, 76 → 60 commits) |
3.2 · 接受的 tradeoff
- filter-repo 是 destructive: 所有 commit SHA 改变, 所有现有 clone 失效, collaborator 必须重 clone. 备份 tag
pre-filter-repo-backup-<ts>保留 24h 应急 - docs/internal/ 仅本地存在: dev-log/dev-plan/PRD/audit 历史价值仅项目主理/collaborator 本机可访问. 跨设备同步需另起私仓 (V1 考虑)
- wiki_aliases.yaml 也 gitignored: 项目重新搭建时, alias 映射必须本地手工补全 (从
.exampleplaceholder 替换)
3.3 · 不变的承诺
- ADR-001 multi-anchor (anchors/<slug>/) 不变
- ADR-003 三形态 D1/D2/D3 不变
- ADR-004 v1 HTTP API 不变
- ADR-005 hybrid deployment 不变
- check_public_safe + redact_check 行为不变 (allowlist 仍含 docs/, 但 docs/internal/ 现在 gitignored 所以非问题)
- 测试 fixture 与 filter rule 仍含真名 (justified self-reference, Layer 4)
3.4 · Invariant impact (新版 ADR 模板要求段)
本变更让以下 invariant 失效:
- "docs/internal/ 在 git 内, 仅 collaborator 可见" → 失效. docs/internal/ 不再 tracked, 仅本机
- "git history 完整保留所有 commit" → 失效. filter-repo 重写 history, 早期 16 commits 合并/删除, 所有 commit SHA 变了
- "config/wiki_aliases.yaml 是 git tracked 配置" → 失效. 移到 .example placeholder 模式
本变更引入以下新 invariant:
- inv-1: HEAD 任何 non-justified-self-reference 文件 0 硬规则命中 (由 P1 + GHA
check-public-safe守住) - inv-2: git history 任何 commit 不含
docs/internal/docs/dev-log/docs/dev-plan*等 13 路径 (由 P2 filter-repo 一次清, 此后由 .gitignore 守住) - inv-3: justified self-reference 文件 (10 个 SKIP_FILES) 含真名必须有 docstring/注释说明 "此处真名为 redact rule pattern / test fixture, 不是泄漏" (人工 review)
4. Alternatives Considered
| 方案 | 为什么没选 |
|---|---|
| A · PRIVATE 不动 | 用户明确想去 public, 早晚做 |
| B · PRIVATE + 仅清 HEAD | history 仍有 docs/internal/ 真名, public 后任何人 git log -p 可见 |
| C · 选择性 sed history 替换 | 复杂 (要 exclude SKIP_FILES from sed), 容易破坏 redact_check 等 pattern 文件 |
| D · BFG 替代 filter-repo | BFG 是老工具, 官方推荐 filter-repo · filter-repo 更精准, 支持 paths-from-file |
| E · 创建 boss-vault-internal 私仓 + submodule | 跨设备同步好, 但 V0 期人手少, 复杂度过高 · 留 V1 |
5. Migration Plan
5.1 · 已执行 (2026-05-28, 本 ADR 触发)
| Phase | 步骤 | Commit |
|---|---|---|
| P1 机械清理 | 27 文件 (C+E cat) 真名变体 → 中性 / <name-variant-*> / placeholder | da73a37 (本次 force push 后 SHA 变, 见 git log) |
| P1.5 gitignore | docs/internal/ + config/wiki_aliases.yaml 加 .gitignore + git rm --cached | 同上 |
| P2 history rewrite | git filter-repo --paths-from-file --invert-paths, 移 13 路径 from history | (rewrite 后 HEAD = 2be53f1 类) |
| P2 force push | 备份 tag + force push origin main | — |
5.2 · Public 切换前 checklist
在把 GitHub repo 从 PRIVATE 切到 PUBLIC 前, 跑这 6 项:
- [ ]
make check-public-safePASS (www/ 0 硬规则) - [ ]
make pre-public-pushPASS (全仓 5 步检查 — Makefile 已有) - [ ]
make lintPASS (skill_lint + redact_check) - [ ]
make test-unitPASS (确认机械替换没破坏机制) - [ ] 人工 review HEAD 任何含真名文件都在 SKIP_FILES (即 inv-3)
- [ ] 通知现有 collaborator: history 已重写, 需
rm -rf .git && git clone重新拿
5.3 · 切 PUBLIC 后监控
- GHA
check-public-safe每次 PR 跑 - pre-commit
redact_check每次 commit 跑 - 月度人工:
python3 scripts/check_public_safe.py --root .全仓硬规则扫一次 (而非默认 www/)
6. 关键反共识立场
"PRIVATE 仓库的真名不算暴露" — 错.
PRIVATE 仓库仍有:
- collaborator 流动风险: 离职/换岗的 collaborator clone 后可永久持有数据
- 意外公开风险: github settings 一键改 PUBLIC, 没有 throttle/审计 (实际事故案例多)
- history travel risk: 即便 HEAD 干净,
git log -p可挖出几个月前的真名
正确立场: 任何 git tracked 的真名都是泄漏风险, 区别只是"对谁泄漏" + "什么时候 trigger". 准备 public 是把这个 risk 真正归 0 的契机.
"filter-repo 重写 history 太重" — 错.
filter-repo:
- 30 秒跑完
- 不可逆只对没备份的 case 危险 — 我们已经
git tag pre-filter-repo-backup-<ts>+ push 到 origin - collaborator re-clone 一次的 cost 远低于 真名 history 永久暴露的 cost
7. 何时回看本 ADR
- 切 PUBLIC 后 1 周: §5.3 监控指标是否健康
- 任何意外回归 (commit 含真名通过 CI): trigger ADR-006 修订, 收紧 invariants
- 引入新 collaborator: 把 §5.2 checklist 加进 onboarding
ADR-006 · accepted · 2026-05-28 · ADR 体系第 6 个 · 准备 GitHub repo 公开化 · 4 层防护 · 60 commits rewritten