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