From 9a24819912544d3fb1dc7db1e9b07b9670045bd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patryk=20G=C3=B3rniak?= Date: Mon, 13 Apr 2026 00:49:42 +0200 Subject: [PATCH 1/2] Securing the script code that performs native summarization of test results and code coverage --- .github/scripts/generate-test-summary.ps1 | 178 +++++++++++++++++----- 1 file changed, 137 insertions(+), 41 deletions(-) diff --git a/.github/scripts/generate-test-summary.ps1 b/.github/scripts/generate-test-summary.ps1 index 6dc07cd..2370f87 100644 --- a/.github/scripts/generate-test-summary.ps1 +++ b/.github/scripts/generate-test-summary.ps1 @@ -1,55 +1,151 @@ -# A simple script that prints a summary of the tests +<# +.SYNOPSIS + Generates a markdown summary of test results and code coverage. +.DESCRIPTION + Safely parses .trx and coverage.cobertura.xml files to extract metrics. + Designed for GitHub Actions CI/CD pipelines. +.PARAMETER WorkspacePath + The root directory of the repository or workspace where the action is executed. Defaults to the GITHUB_WORKSPACE environment variable. +.PARAMETER SummaryPath + The file path where the generated markdown summary will be appended. Defaults to the GITHUB_STEP_SUMMARY environment variable for GitHub Actions. +.PARAMETER TestResultsDir + The name of the directory inside the workspace that contains the .trx and coverage.cobertura.xml files. Defaults to "TestResults". +#> +[CmdletBinding()] param ( + [Parameter(Mandatory = $false)] [string]$WorkspacePath = $env:GITHUB_WORKSPACE, - [string]$SummaryPath = $env:GITHUB_STEP_SUMMARY + + [Parameter(Mandatory = $false)] + [string]$SummaryPath = $env:GITHUB_STEP_SUMMARY, + + [Parameter(Mandatory = $false)] + [string]$TestResultsDir = "TestResults" ) -# If the script is running locally (outside of GitHub Actions), print to the console -if ([string]::IsNullOrEmpty($SummaryPath)) { - $SummaryPath = ".\local-summary-test.md" - Write-Host "Uruchomienie lokalne. Wynik zostanie zapisany do $SummaryPath" -} +# Strict Mode and global error handling +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" -# Retrieving data from a TRX file (test results) -$trxFile = Get-ChildItem -Path "$WorkspacePath/TestResults" -Filter *.trx -Recurse | Select-Object -First 1 -$total = 0; $passed = 0; $failed = 0 - -if ($trxFile) { - [xml]$trx = Get-Content $trxFile.FullName - $counters = $trx.GetElementsByTagName("Counters")[0] - if ($counters) { - $total = $counters.GetAttribute("total") - $passed = $counters.GetAttribute("passed") - $failed = $counters.GetAttribute("failed") +try { + # Safe local path resolution + if ([string]::IsNullOrWhiteSpace($SummaryPath)) { + $SummaryPath = Join-Path -Path $PSScriptRoot -ChildPath "local-summary-test.md" + Write-Warning "Running locally. Summary path not provided. Result will be written to: $SummaryPath" } -} -# Retrieving data from a Cobertura file (code coverage) -$covFile = Get-ChildItem -Path "$WorkspacePath/TestResults" -Filter coverage.cobertura.xml -Recurse | Select-Object -First 1 -$coverageText = "Brak danych" - -if ($covFile) { - [xml]$cov = Get-Content $covFile.FullName - $lineRateStr = $cov.DocumentElement.GetAttribute("line-rate") - if (![string]::IsNullOrWhiteSpace($lineRateStr)) { - $lineRate = [double]::Parse($lineRateStr, [System.Globalization.CultureInfo]::InvariantCulture) - $percent = [math]::Round($lineRate * 100, 2) - $coverageText = "$percent %" + # Workspace path validation + if (-not (Test-Path -Path $WorkspacePath -PathType Container)) { + Write-Warning "Workspace directory not found: $WorkspacePath. Default metrics will be used." } -} + $testResultsPath = Join-Path -Path $WorkspacePath -ChildPath $TestResultsDir + + # Output variables initialization + $total = 0; $passed = 0; $failed = 0 + $coverageText = "No data" + + # Secure function to load XML (XXE Mitigation) + function Get-SecureXml { + param([string]$FilePath) + + $settings = New-Object System.Xml.XmlReaderSettings + $settings.DtdProcessing = [System.Xml.DtdProcessing]::Prohibit + $settings.XmlResolver = $null + + # Read file safely + $reader = [System.Xml.XmlReader]::Create($FilePath, $settings) + $xmlDoc = New-Object System.Xml.XmlDocument + + try { + $xmlDoc.Load($reader) + } + finally { + if ($null -ne $reader) { + $reader.Close() + $reader.Dispose() + } + } + + return $xmlDoc + } + + if (Test-Path -Path $testResultsPath -PathType Container) { + + # Fetching data from the TRX file + $trxFile = Get-ChildItem -Path $testResultsPath -Filter "*.trx" -Recurse -File | Select-Object -First 1 + + if ($null -ne $trxFile) { + try { + Write-Verbose "Parsing TRX file: $($trxFile.FullName)" + $trx = Get-SecureXml -FilePath $trxFile.FullName + $counters = $trx.GetElementsByTagName("Counters") + + if ($null -ne $counters -and $counters.Count -gt 0) { + $counterNode = $counters.Item(0) + + [int]::TryParse($counterNode.GetAttribute("total"), [ref]$total) | Out-Null + [int]::TryParse($counterNode.GetAttribute("passed"), [ref]$passed) | Out-Null + [int]::TryParse($counterNode.GetAttribute("failed"), [ref]$failed) | Out-Null + } + } catch { + Write-Warning "Failed to parse TRX file. Error: $($_.Exception.Message)" + } + } else { + Write-Verbose "No TRX file found in $testResultsPath" + } -$markdown = @" -## πŸ“Š Podsumowanie testΓ³w + # Fetching data from the Cobertura file + $covFile = Get-ChildItem -Path $testResultsPath -Filter "coverage.cobertura.xml" -Recurse -File | Select-Object -First 1 + + if ($null -ne $covFile) { + try { + Write-Verbose "Parsing Cobertura file: $($covFile.FullName)" + $cov = Get-SecureXml -FilePath $covFile.FullName + $lineRateStr = $cov.DocumentElement.GetAttribute("line-rate") + + if (-not [string]::IsNullOrWhiteSpace($lineRateStr)) { + $lineRate = 0.0 + if ([double]::TryParse($lineRateStr, [System.Globalization.NumberStyles]::Any, [System.Globalization.CultureInfo]::InvariantCulture, [ref]$lineRate)) { + $percent = [math]::Round($lineRate * 100, 2) + $coverageText = "$percent %" + } else { + Write-Warning "Failed to parse line-rate value: $lineRateStr" + } + } + } catch { + Write-Warning "Failed to parse Cobertura file. Error: $($_.Exception.Message)" + } + } else { + Write-Verbose "No coverage file found in $testResultsPath" + } + } + + # Summary markdown -| Metryka | Wynik | -|---|---| -| **Pokrycie kodu** | **$coverageText** | -| **Wszystkie testy** | $total | -| Zaliczone βœ… | $passed | -| Nieudane ❌ | $failed | + $markdown = @" + + | Metric | Result | + |---|---| + | **Code coverage** | **$coverageText** | + | **Total tests** | $total | + | Passed βœ… | $passed | + | Failed ❌ | $failed | + "@ -# Write to the file specified by the GitHub environment variable -$markdown | Out-File -FilePath $SummaryPath -Append -Encoding utf8 + # Ensure the target directory exists + $summaryDir = Split-Path -Path $SummaryPath -Parent + if (-not [string]::IsNullOrWhiteSpace($summaryDir) -and -not (Test-Path -Path $summaryDir)) { + New-Item -ItemType Directory -Path $summaryDir -Force | Out-Null + } + + # Write to file + $markdown | Out-File -FilePath $SummaryPath -Append -Encoding utf8 + Write-Verbose "Summary successfully written to $SummaryPath" + +} catch { + Write-Error "A critical error occurred while generating the summary: $($_.Exception.Message)" + exit 1 +} From 60a1870b716d731407ad8ffe1b61348acfcaf861 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patryk=20G=C3=B3rniak?= Date: Mon, 13 Apr 2026 01:41:06 +0200 Subject: [PATCH 2/2] Fix: securing the summary test script code --- .github/scripts/generate-test-summary.ps1 | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/scripts/generate-test-summary.ps1 b/.github/scripts/generate-test-summary.ps1 index 2370f87..8cea54d 100644 --- a/.github/scripts/generate-test-summary.ps1 +++ b/.github/scripts/generate-test-summary.ps1 @@ -122,16 +122,16 @@ try { } } - # Summary markdown +# Summary markdown $markdown = @" - | Metric | Result | - |---|---| - | **Code coverage** | **$coverageText** | - | **Total tests** | $total | - | Passed βœ… | $passed | - | Failed ❌ | $failed | +| Metric | Result | +|---|---| +| **Code coverage** | **$coverageText** | +| **Total tests** | $total | +| Passed βœ… | $passed | +| Failed ❌ | $failed | "@