fix(packaging): 播报 workflow 的种子只在 main 注入 + --key 用法错回 2 + 指针修正(M255 r1 P2-1/2/3)

P2-1(安全):`signing-status.yml` 原先把生产签名种子注入到 `pull_request` 与任意 ref 的
`workflow_dispatch` 运行里,而 CI 跑的是那个 ref 自己的代码 ⇒ 任何能推分支的人都能把种子搬走
(这把种子能给全量已装客户端签可信清单)。改成:
  - 去掉 `pull_request` 触发;
  - 注入行加 ref 门:`github.ref == 'refs/heads/main' && secrets.… || ''`;
  - 非 main 用 `ULL_SIGNING_KEY_WITHHELD` 说明原因,于是 ② 读「未判定(未注入)」而不是「没配」。
runbook §2 步② 加硬顺序:先合这笔、再录 Secret。

P2-2:`--key` 读不了(不存在 / 是目录 / 非 UTF-8)曾在 main 的 try 之外抛 traceback + rc=1,
与「未就绪」同码;现在统一 rc=2 + 一行说明,并补用例 `key_unreadable`。

P2-3:4 处文案指针修正(`signing-ready.yml` → `signing-status.yml`;§3 → §2 步③;§1–§3 →
§2 步①–③;§4 → §2 步⑤ / §5)。新增两条机检:`section_refs`(输出的 `§N` 必须是 runbook 真实
章节号)与 `signing_ready_wiring_selftest.py`(文档里的 `packaging/…`/`crates/…`/`.github/…`
路径必须真存在),后者另带三处变异重放:去掉 ref 门 / 加回 pull_request / 改坏文档路径 ⇒ 必须红。

顺带修掉 r1 复审指出的两处小问题:`--release-assets` 里只有 `manifest.json.sig` 时的子串假读数
(先剥 `.sig` 再判清单)、③ 那个从不参与判定的 bool 保持原样(无行为影响)。

selftest 拆成两份(400 行红线):判据本体 + 接线/文案。guards.yml 仍 399 行零改动。
This commit is contained in:
AskaEth committed 2026-11-11 20:17:04 +08:00
1 parent bff48e27fa
commit bc1b09b776
6 files changed
+397 -86

No files matched your search

+24 -8
View File
@@ -9,7 +9,7 @@ name: Signing status(播报,不是门禁)
# 清单(fail-closed,用户端表现为「更新永远失败」)。工具链早就齐了,缺的是**可见性**:这三行读数让
# 「今天差什么」在每次运行里都摆在台面上,而不是等发版时才发现。
#
# 口径三条(由 `packaging/tests/signing_ready_selftest.py` 的接线判据机器校验,不是靠注释自律):
# 口径四条(由 `packaging/tests/signing_ready_wiring_selftest.py` 的接线判据机器校验,不是靠注释自律):
# ① 就绪度那一步抓 `rc=$?` 之后**恒 `exit 0`** —— 未就绪用 `::warning::` 播报,不用 error 级注解
# (error 会让人以为构建挂了,与播报定位直接冲突)。真正的红留在 `release-publish.yml` 的签名步:
# 那里今天必然红是**对的**,因为签不出来的产物本来就不能发。
@@ -17,6 +17,15 @@ name: Signing status(播报,不是门禁)
# ③ 本文件**刻意不声明 `workflow_call:`**:没有这个触发器,别的 workflow 就没法 `uses:` 它 ——
# 「不能被接进 dev 线变成必需检查」是一条机制,不是一句嘱咐。接线自检另外扫全部 workflow,
# 任何 `uses: …/signing-status.yml` 出现即红(防的是有人先加 workflow_call 再接进来)。
# ④ **生产签名种子只在 `refs/heads/main` 上注入**,且**没有 `pull_request` 触发**(M255 r1 P2-1)。
# 这条是本文件唯一的**安全**口径,别为了「PR 上也能看读数」把它放开:
# - 仓库内分支的 PR / 任意分支的 dispatch **拿得到**仓库 Secret(只有 fork / Dependabot 拿不到);
# - 而这把种子能给**全量已安装客户端**签出被信任的清单(自更新链 = 用户端 RCE),
# 既有设计把它锁在 tag ref 后面(`release.yml` 的 `if: … github.ref_type == 'tag'`)。
# - 播报**不需要**在 PR 上跑:PR 改了 `check_signing_ready.py` 时,CI 会在那个 PR 自己的代码上执行它,
# 若同时注入种子,等于把种子交给任何能推分支的人。
# 门由两行 env 表达(同一个条件、同一个地方可审):钥给 main,非 main 则给 `ULL_SIGNING_KEY_WITHHELD`
# 一个原因串 —— 于是 ② 读成「未判定」而不是「没配」(把策略说成缺配是一次假读数)。
#
# 判据本体与 0/1/2 的语义:`packaging/check_signing_ready.py` 的文件头;
# 人类要做的那几步:`packaging/docs/signing-runbook.md`。
@@ -24,7 +33,7 @@ name: Signing status(播报,不是门禁)
on:
push:
branches: [main]
pull_request:
# ⚠ 刻意**没有** `pull_request`(见口径④)。要读 PR 的签名就绪度,就跑 push 到分支或对着 main dispatch。
workflow_dispatch:
permissions:
@@ -39,8 +48,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: 三行读数 + 0/1/2 语义 + 「退出码改绿必须被抓到」+ 接线不是门禁
- name: 判据本体(三行 / 0/1/2 / 两向退出码变异 / --key 用法错 / 指针章节)
run: python3 packaging/tests/signing_ready_selftest.py
# 接线单独一份(400 行红线):播报不是门禁、**生产种子只在 main 注入**、文档指针真的存在,
# 以及「去掉 ref 门 / 加回 pull_request / 改坏文档路径 ⇒ 必须红」三处变异重放。
- name: 接线(ref 门 / 不被别的 workflow 复用 / 无 error 级注解 / 文档路径存在性 + 三处变异)
run: python3 packaging/tests/signing_ready_wiring_selftest.py
- name: 真跑三种情形(今天的状态 / 假 PinnedKey 副本 / 用法错)
run: |
set -uo pipefail
@@ -78,11 +91,14 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# ② 要读「环境里有没有钥」,所以把 Secret 传进来(与 release-publish.yml 的做法一致)。
# 脚本只回显 key_id 与**公钥**,种子(seed_hex)一个字节都不打印 —— 用例 no_seed_leak 钉着这条。
# ② 要读「环境里有没有钥」,所以把 Secret 传进来 —— **只在 main ref 上**(口径④)。
# 门是下面这两行 env(同一个条件),不是 `if:`:这样非 main 的运行照样跑完三行读数,
# 只是 ② 读成「未判定」,而不是整个 job 被跳过后一片空白。
# ⚠ 这两行是安全边界,接线自检钉着它们(去掉 ref 门 / 加回 pull_request 都会当场红)。
- name: 三行就绪度(脚本给 0/1/2;本步恒 exit 0)
env:
ULL_MANIFEST_SIGNING_KEY: ${{ secrets.ULL_MANIFEST_SIGNING_KEY }}
ULL_MANIFEST_SIGNING_KEY: ${{ github.ref == 'refs/heads/main' && secrets.ULL_MANIFEST_SIGNING_KEY || '' }}
ULL_SIGNING_KEY_WITHHELD: ${{ github.ref == 'refs/heads/main' && '' || '非 main ref(按设计不注入 Secret;要读这一行请对 main 跑)' }}
run: |
set -uo pipefail
set +e
@@ -92,14 +108,14 @@ jobs:
cat "$RUNNER_TEMP/signing-ready.txt"
case "$rc" in
0) echo "::notice::签名就绪:信任表非空,且环境里的钥与它同源。这是一条播报,不是门禁。" ;;
1) echo "::warning::签名未就绪:今天任何正式二进制都验不过任何清单(fail-closed)。该做什么见 packaging/docs/signing-runbook.md §2 的下单清单。" ;;
1) echo "::warning::签名未就绪(或 ② 未判定):该做什么见 packaging/docs/signing-runbook.md §2 的下单清单。" ;;
*) echo "::warning::就绪度脚本用不了(rc=$rc,用法错):这是尺子的问题,不是签名状态 —— 看上面的 stderr。" ;;
esac
{
echo
echo "- 本 workflow 是**播报**,不是门禁:**不应被设为 required**,也不阻断任何其它 job。"
echo "- 颜色 ≠ 结论:绿色只说明脚本跑完了;结论只看上面三行(尤其 ①「信任表」)。"
echo "- fork / Dependabot 的 PR 运行拿不到仓库 Secret ⇒ ② 必然是「未就绪」;以 main 上的运行为准。"
echo "- **Secret 只在 \`refs/heads/main\` 上注入**(生产签名种子锁在 main / tag 后面);非 main ref 的 ② 显示「未判定」而不是「没配」。"
echo "- 真门禁在哪:\`release-publish.yml\` 的签名步(\`--check-pinned-keyring\` / \`--sign\` / \`--verify-pinned\`)。"
} >> "$GITHUB_STEP_SUMMARY"
exit 0