From d39931a01abf24c8280fea810bc6ed3199a2c438 Mon Sep 17 00:00:00 2001 From: kdletters <61648117+kdletters@users.noreply.github.com> Date: Tue, 29 Sep 2026 00:05:54 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8A=8A=E7=AD=BE=E5=90=8D=E5=87=AD=E6=8D=AE?= =?UTF-8?q?=E5=8D=AB=E7=94=9F=E6=94=B6=E8=BF=9B=20check:production-ops=20?= =?UTF-8?q?=E9=97=A8=E7=A6=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 两个 AGC 打包管线必须用 withCredentials 把 AgcUpdaterSigningKey / ...Password 绑到 TAURI_SIGNING_PRIVATE_KEY / ..._PASSWORD - 禁止 echo $TAURI_SIGNING_PRIVATE_KEY、Write-Host $env:...、%VAR%、set -x 之类会把绑定凭据打进日志的写法;归档 glob 不得出现 *.pem|*.key|*.pfx|*.p12 - 变异验证:往 mac 管线插一行 echo $TAURI_SIGNING_PRIVATE_KEY 后门禁立刻以「不得把签名凭据打印到构建日志」失败退出;还原后 check:production-ops OK - 渠道化里程碑第 5 条补记该静态门禁与剩余边界(真实 Jenkins 运行日志仍需 CI 取证) --- ...里程碑】AGC更新发布管线渠道化-2026-09-17.md | 2 +- scripts/check-production-ops-guardrails.mjs | 50 +++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/docs/project-memory/plans/【里程碑】AGC更新发布管线渠道化-2026-09-17.md b/docs/project-memory/plans/【里程碑】AGC更新发布管线渠道化-2026-09-17.md index 98ac5add8..3634989e9 100644 --- a/docs/project-memory/plans/【里程碑】AGC更新发布管线渠道化-2026-09-17.md +++ b/docs/project-memory/plans/【里程碑】AGC更新发布管线渠道化-2026-09-17.md @@ -37,7 +37,7 @@ - [x] 渠道与目标平台不匹配、缺少签名私钥或私钥密码错误时发布失败关闭,不产生半成品清单。 - [x] 发布后 OSS 上安装包、签名与渠道清单三者一致:清单内地址指向已存在的对象,签名与安装包匹配(2026-09-28 只读核对已发布的 `dev-win` / `dev-mac` 渠道,两个渠道的产物都下载后验签通过,见文末「本轮核对」)。 - [x] universal macOS 产物的两个平台键指向同一对象同一签名,不存在只挂单一架构键或指向不存在对象的情况。(清单构建器对 universal 目标仍按此契约工作;但 2026-09-21 决策已把 macOS 发行改成 arm64 单架构,线上 `dev-mac` 按该决策只登记 `darwin-aarch64`,这条的线上口径以 macOS 里程碑第 1 条为准。) -- [ ] Jenkins 归档与日志中不出现签名私钥内容,凭据只注入构建进程。(静态核对:`jenkins/Jenkinsfile.ai-game-creator-shell-build` 用 `withCredentials` 注入 `TAURI_SIGNING_PRIVATE_KEY` / `..._PASSWORD`,归档 glob 只含 `bundle/**/*.exe|*.sig|latest.json|legacy-latest.json|release-notes.txt` 与 `.jenkins-source-commit`,没有私钥文件;真实 Jenkins 运行的日志脱敏仍需一次 CI 构建取证。) +- [ ] Jenkins 归档与日志中不出现签名私钥内容,凭据只注入构建进程。(静态核对:两个 AGC 打包管线都用 `withCredentials` 把 `AgcUpdaterSigningKey` / `...Password` 绑到 `TAURI_SIGNING_PRIVATE_KEY` / `..._PASSWORD`,归档 glob 只含安装包/签名/清单/说明/commit,没有私钥文件。2026-09-29 把这条静态口径收进仓库门禁 `npm run check:production-ops`:缺凭据绑定、或出现 `echo $TAURI_SIGNING_PRIVATE_KEY`、`Write-Host $env:...`、`%TAURI_SIGNING_PRIVATE_KEY%`、`set -x` 之类会打日志的写法、或归档 glob 出现 `*.pem|*.key|*.pfx|*.p12` 都判失败;变异验证:往 mac 管线插一行 `echo $TAURI_SIGNING_PRIVATE_KEY` 后门禁立刻以「不得把签名凭据打印到构建日志」失败退出。真实 Jenkins 运行的日志脱敏仍需一次 CI 构建取证。) - [x] 未显式指定渠道时按目标平台取默认渠道,且 `--no-bundle` smoke 路径仍不读远端版本、不改版本、不生成清单。 ## 证据要求 diff --git a/scripts/check-production-ops-guardrails.mjs b/scripts/check-production-ops-guardrails.mjs index a25f85cc3..10420911d 100644 --- a/scripts/check-production-ops-guardrails.mjs +++ b/scripts/check-production-ops-guardrails.mjs @@ -7794,6 +7794,56 @@ for (const [file, content] of [ } } } + +// 更新签名私钥只允许经 Jenkins 凭据绑定注入构建进程:既不得把凭据本身写进 Jenkinsfile, +// 也不得回显到日志或进归档(对应「AGC 更新发布管线渠道化」第 5 条的可静态复核部分)。 +for (const [file, content] of [ + ['jenkins/Jenkinsfile.ai-game-creator-shell-build', agcPipelineContent], + [ + 'jenkins/Jenkinsfile.ai-game-creator-shell-macos-build', + agcMacosPipelineContent, + ], +]) { + for (const [snippet, reason] of [ + [ + "string(credentialsId: 'AgcUpdaterSigningKey', variable: 'TAURI_SIGNING_PRIVATE_KEY')", + '签名私钥必须经 `AgcUpdaterSigningKey` 凭据绑定注入 `TAURI_SIGNING_PRIVATE_KEY`。', + ], + [ + "string(credentialsId: 'AgcUpdaterSigningKeyPassword', variable: 'TAURI_SIGNING_PRIVATE_KEY_PASSWORD')", + '私钥口令必须经 `AgcUpdaterSigningKeyPassword` 凭据绑定注入。', + ], + ]) { + if (!content.includes(snippet)) { + failed = true; + console.error(`[check:production-ops] ${file} ${reason}`); + } + } + for (const leak of [ + 'echo $TAURI_SIGNING_PRIVATE_KEY', + 'echo "$TAURI_SIGNING_PRIVATE_KEY"', + 'echo ${TAURI_SIGNING_PRIVATE_KEY}', + 'echo "$env:TAURI_SIGNING_PRIVATE_KEY"', + 'Write-Host $env:TAURI_SIGNING_PRIVATE_KEY', + 'Write-Output $env:TAURI_SIGNING_PRIVATE_KEY', + '%TAURI_SIGNING_PRIVATE_KEY%', + 'set -x', + ]) { + if (content.includes(leak)) { + failed = true; + console.error( + `[check:production-ops] ${file} 不得把签名凭据打印到构建日志(命中 \`${leak}\`,xtrace 会把绑定的凭据一起打出来)。`, + ); + } + } + const archiveBlock = content.split('archiveArtifacts')[1] ?? ''; + if (/(\.pem|\.key|\.pfx|\.p12)\b/u.test(archiveBlock.slice(0, 600))) { + failed = true; + console.error( + `[check:production-ops] ${file} 归档 glob 不得包含签名私钥类文件(*.pem / *.key / *.pfx / *.p12)。`, + ); + } +} for (const [snippet, reason] of [ [ "string(name: 'OSS_DOWNLOAD_URL'",