@@ -117,6 +117,76 @@ Describe 'DevNav updater lifecycle' {
117117 Get-ChildItem $installRoot - Filter ' *.bak' | Should - BeNullOrEmpty
118118 }
119119
120+ It ' downloads x64 release assets only from canonical GitHub URLs' {
121+ $global :DevNavDownloadUris = [System.Collections.Generic.List [string ]]::new()
122+ $devModule.Invoke ({
123+ Mock Get-DevInstalledVersion { $global :DevNavTestPreviousVersion }
124+ Mock Get-DevLatestRelease { $global :DevNavTestRelease }
125+ Mock Invoke-WebRequest {
126+ $global :DevNavDownloadUris.Add ($Uri )
127+ Copy-Item (Join-Path $global :DevNavTestSourceRoot ([IO.Path ]::GetFileName(([uri ]$Uri ).AbsolutePath))) $OutFile
128+ }
129+ Update-DevNavigator - Confirm:$false
130+ })
131+ $expectedBase = " https://github.com/JacobOptimiza/dev-nav/releases/download/v$ ( $global :DevNavTestRelease.tag_name.TrimStart (' v' )) "
132+ $global :DevNavDownloadUris | Should - Be @ (
133+ " $expectedBase /dev-windows-x86_64.exe" ,
134+ " $expectedBase /DevNav.psm1" ,
135+ " $expectedBase /SHA256SUMS.txt"
136+ )
137+ }
138+
139+ It ' constructs the canonical ARM64 release asset URL' {
140+ $devModule.Invoke ({
141+ Get-DevReleaseAssetUrl - Tag ' v0.13.0' - AssetName ' dev-windows-aarch64.exe'
142+ }) | Should - Be ' https://github.com/JacobOptimiza/dev-nav/releases/download/v0.13.0/dev-windows-aarch64.exe'
143+ }
144+
145+ It ' rejects a release tag outside the supported vMAJOR.MINOR.PATCH format before downloading' {
146+ $invalidRelease = [pscustomobject ]@ {
147+ tag_name = ' v0.13.0-preview'
148+ assets = $global :DevNavTestRelease.assets
149+ }
150+ $global :DevNavInvalidRelease = $invalidRelease
151+ $global :DevNavDownloadAttempted = $false
152+ $devModule.Invoke ({
153+ Mock Get-DevInstalledVersion { $global :DevNavTestPreviousVersion }
154+ Mock Get-DevLatestRelease { $global :DevNavInvalidRelease }
155+ Mock Invoke-WebRequest { $global :DevNavDownloadAttempted = $true }
156+ { Update-DevNavigator - Confirm:$false } | Should - Throw ' *tag no compatible*'
157+ })
158+ $global :DevNavDownloadAttempted | Should - BeFalse
159+ }
160+
161+ It ' ignores malicious metadata URLs and never constructs a download for an unallowlisted asset' {
162+ $global :DevNavDownloadUris = [System.Collections.Generic.List [string ]]::new()
163+ $maliciousRelease = [pscustomobject ]@ {
164+ tag_name = $global :DevNavTestRelease.tag_name
165+ assets = @ (
166+ [pscustomobject ]@ {name = ' dev-windows-x86_64.exe' ; browser_download_url = ' https://evil.example/payload.exe' },
167+ [pscustomobject ]@ {name = ' DevNav.psm1' ; browser_download_url = ' http://github.com/DevNav.psm1' },
168+ [pscustomobject ]@ {name = ' SHA256SUMS.txt' ; browser_download_url = ' https://github.com.evil.example/SHA256SUMS.txt' },
169+ [pscustomobject ]@ {name = ' payload.exe' ; browser_download_url = ' https://github.com/other/repo/releases/download/v9.9.9/payload.exe' }
170+ )
171+ }
172+ $global :DevNavMaliciousRelease = $maliciousRelease
173+ $devModule.Invoke ({
174+ Mock Get-DevInstalledVersion { $global :DevNavTestPreviousVersion }
175+ Mock Get-DevLatestRelease { $global :DevNavMaliciousRelease }
176+ Mock Invoke-WebRequest {
177+ $global :DevNavDownloadUris.Add ($Uri )
178+ Copy-Item (Join-Path $global :DevNavTestSourceRoot ([IO.Path ]::GetFileName(([uri ]$Uri ).AbsolutePath))) $OutFile
179+ }
180+ Update-DevNavigator - Confirm:$false
181+ { Get-DevReleaseAssetUrl - Tag ' v0.13.0' - AssetName ' payload.exe' } | Should - Throw ' *no está permitido*'
182+ })
183+ $global :DevNavDownloadUris | Should -Not - Contain ' https://evil.example/payload.exe'
184+ $global :DevNavDownloadUris | Should -Not - Contain ' http://github.com/DevNav.psm1'
185+ $global :DevNavDownloadUris | Should -Not - Contain ' https://github.com.evil.example/SHA256SUMS.txt'
186+ $global :DevNavDownloadUris | Should -Not -Match ' other/repo|payload.exe'
187+ @ ($global :DevNavDownloadUris | Where-Object { $_ -notmatch ' ^https://github\.com/JacobOptimiza/dev-nav/releases/download/v\d+\.\d+\.\d+/(dev-windows-x86_64\.exe|DevNav\.psm1|SHA256SUMS\.txt)$' }).Count | Should - Be 0
188+ }
189+
120190 It ' marks restart required when the module changes' {
121191 Add-Content - LiteralPath (Join-Path $sourceRoot ' DevNav.psm1' ) - Value " `n # update marker"
122192 $moduleHash = (Get-FileHash (Join-Path $sourceRoot ' DevNav.psm1' ) - Algorithm SHA256).Hash
0 commit comments