From e17cdcda79407ebba1590ed7c79d0a328f40d638 Mon Sep 17 00:00:00 2001 From: Shuery <2463253700@qq.com> Date: Sat, 26 Sep 2026 22:41:52 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=8F=A3=E4=BB=A4=E4=BC=9A=E9=9A=8F=20-?= =?UTF-8?q?Verbose=20=E8=90=BD=E8=BF=9B=E6=97=A5=E5=BF=97=EF=BC=88DEBUG=20?= =?UTF-8?q?=E4=B8=8B=E6=89=93=E5=8D=B0=E6=95=B4=E6=9D=A1=E5=91=BD=E4=BB=A4?= =?UTF-8?q?=E8=A1=8C=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 缺陷:Invoke-ExternalCommand 在 DEBUG 级打印整条命令行,而 7z / RAR 只接受命令行 口令(-p<口令>),所以口令必然出现在参数表里。一旦 -Verbose(Backup.ps1 / Restore.ps1 都会因它打开 DEBUG),logs\*.log 里就是明文口令 —— 与 BackupConfig.psd1 和文档里 "口令不落盘、不写进仓库"的承诺直接冲突。 修法:打印前把 -p 参数换成占位符;真正执行的仍然是原参数。在**参数级别**替换而不是 对拼好的命令行做正则 —— 含空格的口令会被引号包起来("-pmy pass"),正则在那种形态上 很容易漏掉,而漏掉的代价是口令明文入日志。 回归断言(零依赖与 Pester 各一份):打开 DEBUG、把日志指向临时目录、带一个哨兵口令 跑一次外部命令,然后读日志文件断言哨兵不在里面、占位符在里面。另加一条"测试的测试": 断言那行 DEBUG 记录确实写进去了 —— 否则前两条会在"压根没记录"时空跑通过。 断言有效性做了红绿证明:把遮蔽改回 $startInfo.Arguments 后断言变红并指名"口令明文 进了日志",还原后转绿。 验收:test.ps1 9/9 全绿;tests\Run-RealSmoke.ps1 4/4 全绿。 --- Common.psm1 | 12 +++++++++++- tests/BakNRet.Tests.ps1 | 23 +++++++++++++++++++++++ tests/Run-Tests.ps1 | 25 +++++++++++++++++++++++++ 3 files changed, 59 insertions(+), 1 deletion(-) diff --git a/Common.psm1 b/Common.psm1 index 2a4e5eb..332cc97 100644 --- a/Common.psm1 +++ b/Common.psm1 @@ -262,7 +262,17 @@ function Invoke-ExternalCommand { $startInfo.WorkingDirectory = $WorkingDirectory } - Write-Log ('执行: {0} {1}' -f $FilePath, $startInfo.Arguments) -Level DEBUG + # 打印的那一行必须把口令遮蔽掉:7z / RAR 只接受命令行口令(`-p<口令>`),所以口令 + # 必然出现在参数表里;一旦 -Verbose 打开 DEBUG,整条命令行就会落进 logs\*.log —— + # 而 BackupConfig.psd1 与文档都承诺过"口令不落盘、不写进仓库"。真正执行的仍然是 + # $startInfo.Arguments,这里只改日志。 + # + # 在**参数级别**遮蔽,而不是对拼好的命令行做正则:含空格的口令会被引号包起来 + # ("-pmy pass"),正则在那种形态上很容易漏掉,而漏掉的代价是口令明文入日志。 + $loggableArguments = @($ArgumentList | ForEach-Object { + if ($_ -is [string] -and $_ -like '-p*') { '-p<口令已隐藏>' } else { $_ } + }) + Write-Log ('执行: {0} {1}' -f $FilePath, (ConvertTo-NativeArgumentString -ArgumentList $loggableArguments)) -Level DEBUG $process = [System.Diagnostics.Process]::Start($startInfo) try { diff --git a/tests/BakNRet.Tests.ps1 b/tests/BakNRet.Tests.ps1 index bbf8dc5..59f5b53 100644 --- a/tests/BakNRet.Tests.ps1 +++ b/tests/BakNRet.Tests.ps1 @@ -1116,6 +1116,29 @@ Describe 'Resolve-BackupEntry:条目解析' { Describe '外部命令退出码(旧实现的核心缺陷)' { # ============================================================================ + It '口令不会进日志:DEBUG 下打印的命令行要遮蔽 -p 参数' { + $sandbox = Join-Path $env:TEMP ("bnr-secret-" + [guid]::NewGuid().ToString('N').Substring(0, 8)) + New-Item -ItemType Directory -Path $sandbox -Force | Out-Null + $sentinel = 'SENTINEL-PW-9f3a' + $logPath = $null + try { + $logPath = Start-BaknretLog -Directory $sandbox -Prefix 'secret' + Set-BaknretDebug + $null = Invoke-ExternalCommand -FilePath 'cmd.exe' -ArgumentList @('/c', 'exit 0', "-p$sentinel") + } finally { + Set-BaknretDebug -Enabled:$false + Stop-BaknretLog + } + + $logText = Get-Content -Encoding UTF8 -LiteralPath $logPath -Raw + # 这条是"测试的测试":没有它,万一 DEBUG 那行压根没写进去,后面两条会空跑通过 + $logText | Should -Match '执行:' + $logText | Should -Not -Match ([regex]::Escape($sentinel)) + $logText | Should -Match '口令已隐藏' + + Remove-Item -LiteralPath $sandbox -Recurse -Force -ErrorAction SilentlyContinue + } + It 'Invoke-ExternalCommand 能拿到真实退出码' { (Invoke-ExternalCommand -FilePath 'cmd.exe' -ArgumentList @('/c', 'exit 7')) | Should -Be 7 } diff --git a/tests/Run-Tests.ps1 b/tests/Run-Tests.ps1 index 3f5e996..e2e8ba3 100644 --- a/tests/Run-Tests.ps1 +++ b/tests/Run-Tests.ps1 @@ -1191,6 +1191,31 @@ if (-not $sevenZip) { Write-Host "`n== 外部命令退出码 ==" -ForegroundColor Cyan # ============================================================================ +Test-Case '口令不会进日志:DEBUG 下打印的命令行要遮蔽 -p 参数' { + # 7z / RAR 只接受命令行口令,所以口令必然出现在参数表里。一旦 -Verbose 打开 DEBUG, + # 整条命令行就会落进 logs\*.log —— 而 BackupConfig.psd1 与文档都承诺过"口令不落盘"。 + $sandbox = Join-Path $env:TEMP ("bnr-secret-" + [guid]::NewGuid().ToString('N').Substring(0, 8)) + New-Item -ItemType Directory -Path $sandbox -Force | Out-Null + $sentinel = 'SENTINEL-PW-9f3a' + $logPath = $null + try { + $logPath = Start-BaknretLog -Directory $sandbox -Prefix 'secret' + Set-BaknretDebug + $null = Invoke-ExternalCommand -FilePath 'cmd.exe' -ArgumentList @('/c', 'exit 0', "-p$sentinel") + } finally { + Set-BaknretDebug -Enabled:$false + Stop-BaknretLog + } + + $logText = Get-Content -Encoding UTF8 -LiteralPath $logPath -Raw + # 这条是"测试的测试":没有它,万一 DEBUG 那行压根没写进去,后面两条会空跑通过 + Assert-True ($logText -match '执行:') '日志里没有那行 DEBUG 的命令行记录,这条断言就是空跑' + Assert-False ($logText -match [regex]::Escape($sentinel)) '口令明文进了日志' + Assert-True ($logText -match '口令已隐藏') '日志里应当出现遮蔽占位符' + + Remove-Item -LiteralPath $sandbox -Recurse -Force -ErrorAction SilentlyContinue +} + Test-Case 'Invoke-ExternalCommand 能拿到真实退出码(旧实现用 Start-Process 拿不到)' { $code = Invoke-ExternalCommand -FilePath 'cmd.exe' -ArgumentList @('/c', 'exit 7') Assert-Equal 7 $code