diff --git a/scripts/external-review-pack.ps1 b/scripts/external-review-pack.ps1 index c465c2f8..b3cd839b 100644 --- a/scripts/external-review-pack.ps1 +++ b/scripts/external-review-pack.ps1 @@ -6,30 +6,47 @@ param( $ErrorActionPreference = "Stop" +function Assert-ExternalReviewDateStamp { + <# + .SYNOPSIS + Reject DateStamp values that are not YYYY-MM-DD (#1192). + #> + param( + [Parameter(Mandatory = $true)] + [string]$Value + ) + if ($Value -notmatch '^\d{4}-\d{2}-\d{2}$') { + throw "Invalid -DateStamp '$Value' - expected YYYY-MM-DD." + } +} + if ([string]::IsNullOrWhiteSpace($DateStamp)) { $DateStamp = (Get-Date).ToString("yyyy-MM-dd") } +Assert-ExternalReviewDateStamp -Value $DateStamp + $bundlePath = "docs/private/gemini_bundles/public_bundle_$DateStamp.txt" Write-Host "=== External review pack ===" Write-Host "Bundle date: $DateStamp" Write-Host "" -$cmd = @( - "uv run python scripts/export_public_gemini_bundle.py", - "--output `"$bundlePath`"", +$argv = @( + "run", + "python", + "scripts/export_public_gemini_bundle.py", + "--output", $bundlePath, "--compliance-yaml", "--verify" ) -if ($IncludePlans) { $cmd += "--plans" } -if ($IncludeCursor) { $cmd += "--cursor" } +if ($IncludePlans) { $argv += "--plans" } +if ($IncludeCursor) { $argv += "--cursor" } -$joined = $cmd -join " " Write-Host "[1/3] Building Gemini bundle" -Write-Host $joined -Invoke-Expression $joined +Write-Host "uv $($argv -join ' ')" +& uv @argv if ($LASTEXITCODE -ne 0) { throw "Bundle generation/verification failed (exit code $LASTEXITCODE)." } diff --git a/tests/pester/ExternalReviewPack.Tests.ps1 b/tests/pester/ExternalReviewPack.Tests.ps1 new file mode 100644 index 00000000..559a1101 --- /dev/null +++ b/tests/pester/ExternalReviewPack.Tests.ps1 @@ -0,0 +1,37 @@ +BeforeAll { + . (Join-Path $PSScriptRoot '_RepoRoot.ps1') + $script:PackScript = Get-RepoScript 'scripts/external-review-pack.ps1' +} + +Describe 'external-review-pack.ps1 (#1192)' { + It 'does not use Invoke-Expression; validates DateStamp and calls uv via splat' { + $raw = Get-ScriptRaw 'scripts/external-review-pack.ps1' + $raw | Should -Not -Match 'Invoke-Expression' + $raw | Should -Match '& uv @argv' + $raw | Should -Match 'Assert-ExternalReviewDateStamp' + $raw | Should -Match ([regex]::Escape('^\d{4}-\d{2}-\d{2}$')) + } + + It 'Accepts a valid YYYY-MM-DD DateStamp (regex guard)' { + # Mirror Assert-ExternalReviewDateStamp — valid path only; avoid running uv/bundle. + $Value = '2026-07-15' + { if ($Value -notmatch '^\d{4}-\d{2}-\d{2}$') { + throw "Invalid -DateStamp '$Value' - expected YYYY-MM-DD." + } + } | Should -Not -Throw + } + + It 'Throws on injection-shaped DateStamp before calling uv' { + $bad = '2026-01-01"; evil' + { & $script:PackScript -DateStamp $bad } | Should -Throw -Because 'injection must fail closed before & uv' + } + + It 'Throws on non-date DateStamp before calling uv' { + { & $script:PackScript -DateStamp 'abc' } | Should -Throw + } + + It 'Throw message names YYYY-MM-DD for invalid DateStamp' { + { & $script:PackScript -DateStamp 'not-a-date' } | + Should -Throw -ExpectedMessage '*YYYY-MM-DD*' + } +}