fix: v2.2.1 - Top5 鲁棒化修复 (F-09/F-12/F-17/F-19/F-22)

F-22: Phase 4 解密成功后调用 Save-SecretsToCache, 下次免授权码
F-12: 解密结果写入 env 前校验白名单 + 长度 < 512
F-19: 冲突检测改逐行匹配, 修复 Out-String 多行正则失效
F-09: boot 仓库 clone 始终弹凭证对话框, 不依赖 $cred 残留
F-17: uvx tool install 参数顺序修正 (包名在前 --python 在后)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
bookworm 2026-04-10 22:29:04 +08:00
parent 50d3ef0377
commit edee7b777e

View File

@ -16,7 +16,7 @@ param(
$ErrorActionPreference = "Stop" $ErrorActionPreference = "Stop"
# ─── 版本号 (每次更新递增, build.ps1 自动读取) ────── # ─── 版本号 (每次更新递增, build.ps1 自动读取) ──────
$BWVersion = "2.2.0" $BWVersion = "2.2.1"
# ─── B4: 单实例保护 (防止双击两次导致竞态) ───────── # ─── B4: 单实例保护 (防止双击两次导致竞态) ─────────
$mutexCreated = $false $mutexCreated = $false
@ -964,7 +964,8 @@ if (Test-Path (Join-Path $BootDir ".git")) {
} catch { Log-Warn "boot 仓库更新失败, 使用本地版本" } } catch { Log-Warn "boot 仓库更新失败, 使用本地版本" }
} else { } else {
Log-Info "克隆 boot 仓库 (含解密工具与凭证)..." Log-Info "克隆 boot 仓库 (含解密工具与凭证)..."
if (-not $cred) { $cred = Show-GiteaCredentialDialog } # F-09 fix: 始终弹凭证对话框, 不依赖 config 分支的 $cred 残留值
$cred = Show-GiteaCredentialDialog
$bootCloneUrl = if ($cred) { $BootUrl -replace '://', "://$([System.Uri]::EscapeDataString($cred.User)):$([System.Uri]::EscapeDataString($cred.Pass))@" } else { $BootUrl } $bootCloneUrl = if ($cred) { $BootUrl -replace '://', "://$([System.Uri]::EscapeDataString($cred.User)):$([System.Uri]::EscapeDataString($cred.Pass))@" } else { $BootUrl }
$r = Run-CmdWithUI "git" @("clone", "--depth", "1", $bootCloneUrl, $BootDir) "克隆 boot 仓库" 180000 $r = Run-CmdWithUI "git" @("clone", "--depth", "1", $bootCloneUrl, $BootDir) "克隆 boot 仓库" 180000
if (-not (Test-Path (Join-Path $BootDir "crypto-helper.js"))) { if (-not (Test-Path (Join-Path $BootDir "crypto-helper.js"))) {
@ -973,6 +974,7 @@ if (Test-Path (Join-Path $BootDir ".git")) {
exit 1 exit 1
} }
Log-OK "boot 仓库克隆成功 → $BootDir" Log-OK "boot 仓库克隆成功 → $BootDir"
Cache-GitCredentials $cred # F-09 fix: 缓存 boot 仓库凭证
} }
# ======================================================================== # ========================================================================
@ -1059,16 +1061,19 @@ elseif ((Test-Path $SecretsEnc) -or (Get-ChildItem $BootDir -Filter "secrets-*.e
if (-not $line -or $line -notmatch '=') { continue } if (-not $line -or $line -notmatch '=') { continue }
$key = ($line -split '=', 2)[0].Trim() $key = ($line -split '=', 2)[0].Trim()
$value = ($line -split '=', 2)[1].Trim() $value = ($line -split '=', 2)[1].Trim()
if ($key -and $value) { # F-12 fix: 白名单 + 长度校验, 防止 secrets.enc 被污染注入恶意 env
# 写入 Process (当前进程立即生效) + User (永久, 新终端也可用) if ($key -and $value -and ($key -in $CacheAllowedKeys) -and ($value.Length -lt 512)) {
[System.Environment]::SetEnvironmentVariable($key, $value, "Process") [System.Environment]::SetEnvironmentVariable($key, $value, "Process")
[System.Environment]::SetEnvironmentVariable($key, $value, "User") [System.Environment]::SetEnvironmentVariable($key, $value, "User")
Log-OK "已注入: $key (永久)" Log-OK "已注入: $key (永久)"
$count++ $count++
} elseif ($key -and ($key -notin $CacheAllowedKeys)) {
Bw-Log "WARN" "跳过未知 key: $key (不在白名单)"
} }
} }
$decrypted = $null $decrypted = $null
$secretsDecrypted = $true $secretsDecrypted = $true
Save-SecretsToCache # F-22 fix: 写入 DPAPI 缓存, 下次免授权码
Show-MsgBox "授权码验证成功!`n`n$count 个凭证已写入系统环境变量 (永久生效)。`n任何终端输入 claude 即可启动,无需再次输入授权码。" "验证成功" "OK" "Information" Show-MsgBox "授权码验证成功!`n`n$count 个凭证已写入系统环境变量 (永久生效)。`n任何终端输入 claude 即可启动,无需再次输入授权码。" "验证成功" "OK" "Information"
break break
@ -1307,14 +1312,15 @@ try {
if (Test-Cmd "uvx") { if (Test-Cmd "uvx") {
Log-Info "Python MCP 验证 (uvx)..." Log-Info "Python MCP 验证 (uvx)..."
$uvxPackages = @( $uvxPackages = @(
@{ Name = "windows-mcp"; Args = @("--python", "3.13", "windows-mcp") } # F-17 fix: uv tool install 参数顺序 = 包名在前, --python 在后
@{ Name = "atlassian"; Args = @("mcp-atlassian") } @{ Name = "windows-mcp"; Args = @("tool", "install", "windows-mcp", "--python", "3.13") }
@{ Name = "atlassian"; Args = @("tool", "install", "mcp-atlassian") }
) )
foreach ($pkg in $uvxPackages) { foreach ($pkg in $uvxPackages) {
try { try {
$outTmp = Join-Path $env:TEMP "bw-uvx-$($pkg.Name).tmp" $outTmp = Join-Path $env:TEMP "bw-uvx-$($pkg.Name).tmp"
$errTmp = Join-Path $env:TEMP "bw-uvx-$($pkg.Name)-err.tmp" $errTmp = Join-Path $env:TEMP "bw-uvx-$($pkg.Name)-err.tmp"
$installArgs = @("tool", "install") + $pkg.Args $installArgs = $pkg.Args
$proc = Start-Process uv -ArgumentList $installArgs ` $proc = Start-Process uv -ArgumentList $installArgs `
-NoNewWindow -PassThru ` -NoNewWindow -PassThru `
-RedirectStandardOutput $outTmp ` -RedirectStandardOutput $outTmp `
@ -1387,8 +1393,10 @@ try {
try { try {
$claudeGit = Join-Path $ClaudeDir ".git" $claudeGit = Join-Path $ClaudeDir ".git"
if (Test-Path $claudeGit) { if (Test-Path $claudeGit) {
$gitStatus = & git -C $ClaudeDir status --porcelain 2>&1 | Out-String # F-19 fix: 逐行匹配冲突状态 (Out-String 合并后 ^ 不匹配行内)
if ($gitStatus -match '^U|^.U') { $gitLines = & git -C $ClaudeDir status --porcelain 2>&1
$hasConflict = $gitLines | Where-Object { $_ -match '^U|^.U' }
if ($hasConflict) {
& git -C $ClaudeDir checkout --theirs . 2>&1 | Out-Null & git -C $ClaudeDir checkout --theirs . 2>&1 | Out-Null
& git -C $ClaudeDir add -A 2>&1 | Out-Null & git -C $ClaudeDir add -A 2>&1 | Out-Null
& git -C $ClaudeDir commit -m "auto-resolve merge conflicts" 2>&1 | Out-Null & git -C $ClaudeDir commit -m "auto-resolve merge conflicts" 2>&1 | Out-Null