From c80e204624c1638d015522237d7c1bdd185322e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?FabioLeit=C3=A3o?= Date: Wed, 15 Jul 2026 13:20:32 -0300 Subject: [PATCH] fix(security): drop Invoke-Expression from external-review-pack.ps1 Validate -DateStamp as YYYY-MM-DD and invoke uv via an arg array so operator DateStamp input cannot reach shell parsing. Closes #1192 --- scripts/external-review-pack.ps1 | 33 +++++++++++++++----- tests/pester/ExternalReviewPack.Tests.ps1 | 37 +++++++++++++++++++++++ 2 files changed, 62 insertions(+), 8 deletions(-) create mode 100644 tests/pester/ExternalReviewPack.Tests.ps1 diff --git a/scripts/external-review-pack.ps1 b/scripts/external-review-pack.ps1 index c465c2f85..b3cd839be 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 000000000..559a11015 --- /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*' + } +}