Tests/Pester.PSSecRules.Tests.ps1
|
BeforeAll { function RunRuleForCommand { param([String] $Command) $outputPath = Join-Path $env:TEMP ([IO.Path]::GetRandomFileName() + ".ps1") try { Set-Content -Path $outputPath -Value $Command Invoke-ScriptAnalyzer -Path $outputPath ` -CustomizedRulePath (Resolve-Path $PSScriptRoot\..\PSSecRules.psm1) ` -ExcludeRule PS* } finally { Remove-Item $outputPath -ErrorAction SilentlyContinue } } function RunRuleForFile { param([String] $RelativePath) $filePath = Join-Path $PSScriptRoot "..\Examples\$RelativePath" Invoke-ScriptAnalyzer -Path $filePath ` -CustomizedRulePath (Resolve-Path $PSScriptRoot\..\PSSecRules.psm1) ` -ExcludeRule PS* } } Describe "Tests for PSSec.HardcodedCredential" { It "Should detect hardcoded password assignment" { $result = RunRuleForCommand { function Test-HardcodedCredential { $password = "sRbHG$a%" return $password.Length } } $result.RuleName | Should -Be "PSSec.HardcodedCredential" } It "Should detect hardcoded default credential parameter" { $result = RunRuleForCommand { function Test-HardcodedCredential { param([string]$ClientSecret = "my-fixed-secret") return $ClientSecret } } $result.RuleName | Should -Be "PSSec.HardcodedCredential" } It "Should detect hardcoded credential comparison" { $result = RunRuleForCommand { function Test-Login { param($userName, $pwdInput) if($userName -eq "admin" -and $pwdInput -eq "sRbHG$a%") { return $true } return $false } } $result.RuleName | Should -Be "PSSec.HardcodedCredential" } } Describe "Tests for PSSec.SqlInjection" { It "Should detect dynamic SQL with string interpolation" { $result = RunRuleForCommand { function Invoke-UnsafeSql { param($userInput) $query = "SELECT * FROM Users WHERE UserName = '$userInput'" Invoke-Sqlcmd -Query $query } } $result.RuleName | Should -Be "PSSec.SqlInjection" } It "Should detect CommandText assignment with concatenation" { $result = RunRuleForCommand { function Invoke-UnsafeSql { param($userInput) $cmd = New-Object psobject $cmd.CommandText = "SELECT * FROM Users WHERE UserName='" + $userInput + "'" return $cmd } } $result.RuleName | Should -Be "PSSec.SqlInjection" } } Describe "Tests for PSSec.PathTraversal" { It "Should detect tainted variable used as Get-Content path" { $result = RunRuleForCommand { function Get-UnsafeFileContent { param($pathInput) Get-Content -Path $pathInput } } $result.RuleName | Should -Be "PSSec.PathTraversal" } It "Should detect tainted variable in .NET file API" { $result = RunRuleForCommand { function Test-EngineSchema { param($pathInput) if($null -ne $pathInput) { $json = [System.IO.File]::ReadAllText($pathInput) return $json } } } $result.RuleName | Should -Be "PSSec.PathTraversal" } } Describe "Tests for PSSec.Xss" { It "Should detect tainted URL passed to Navigate" { $result = RunRuleForCommand { function Open-HtmlPage { param($urlQuery) $urlRequest = "http://mysite.com?" + $urlQuery $browser = New-Object psobject $browser.Navigate($urlRequest) } } $result.RuleName | Should -Be "PSSec.Xss" } It "Should detect tainted comment rendered via Write" { $result = RunRuleForCommand { function Show-Comment { param($userComment) $response = New-Object psobject $response.Write($userComment) } } $result.RuleName | Should -Be "PSSec.Xss" } It "Should allow encoded output before sink" { $result = RunRuleForCommand { function Open-HtmlPage { param($urlQuery) $encoded = [System.Net.WebUtility]::HtmlEncode($urlQuery) $browser = New-Object psobject $browser.Navigate($encoded) } } $result | Should -Be $null } } Describe "Tests for PSSec.InsecureDeserialization" { It "Should detect tainted json passed to JsonSerializer.Deserialize" { $result = RunRuleForCommand { function Test-EngineSchema { param($pathInput) if($null -ne $pathInput) { $json = [System.IO.File]::ReadAllText($pathInput) $schema = [System.Text.Json.JsonSerializer]::Deserialize($json) return $schema } } } $result.RuleName | Should -Be "PSSec.InsecureDeserialization" } It "Should detect tainted input in ConvertFrom-Json" { $result = RunRuleForCommand { function Read-Config { param($jsonInput) $obj = ConvertFrom-Json -InputObject $jsonInput return $obj } } $result.RuleName | Should -Be "PSSec.InsecureDeserialization" } It "Should allow constant json deserialization" { $result = RunRuleForCommand { function Read-Config { $json = '{"name":"safe"}' $obj = ConvertFrom-Json -InputObject $json return $obj } } $result | Should -Be $null } } Describe "Tests for PSSec.OldTlsProtocol" { It "Should detect old TLS protocol enum" { $result = RunRuleForCommand { function Invoke-ClientAuth { $protocol = [System.Security.Authentication.SslProtocols]::Tls return $protocol } } $result.RuleName | Should -Be "PSSec.OldTlsProtocol" } It "Should allow TLS 1.2" { $result = RunRuleForCommand { function Invoke-ClientAuth { $protocol = [System.Security.Authentication.SslProtocols]::Tls12 return $protocol } } $result | Should -Be $null } } Describe "Tests for PSSec.OutdatedCrypto" { It "Should detect SHA1 provider" { $result = RunRuleForCommand { function Get-HashValue { param($text) $enc = [System.Text.Encoding]::UTF8 $buffer = $enc.GetBytes($text) $sha1 = New-Object System.Security.Cryptography.SHA1CryptoServiceProvider return $sha1.ComputeHash($buffer) } } $result.RuleName | Should -Be "PSSec.OutdatedCrypto" } It "Should allow SHA256 provider" { $result = RunRuleForCommand { function Get-HashValue { param($text) $enc = [System.Text.Encoding]::UTF8 $buffer = $enc.GetBytes($text) $sha256 = New-Object System.Security.Cryptography.SHA256CryptoServiceProvider return $sha256.ComputeHash($buffer) } } $result | Should -Be $null } } Describe "Tests for PSSec.Xxe" { It "Should detect insecure XmlDocument settings with tainted input" { $result = RunRuleForCommand { function Read-XmlUnsafe { param($xmlPathInput) $doc = New-Object System.Xml.XmlDocument $doc.XmlResolver = New-Object System.Xml.XmlUrlResolver $doc.Load($xmlPathInput) return $doc.InnerText } } $result.RuleName | Should -Be "PSSec.Xxe" } It "Should detect insecure XmlReaderSettings with DTD parse" { $result = RunRuleForCommand { function Read-XmlUnsafe { param($xmlPathInput) $settings = New-Object System.Xml.XmlReaderSettings $settings.XmlResolver = New-Object System.Xml.XmlUrlResolver $settings.DtdProcessing = [System.Xml.DtdProcessing]::Parse $reader = [System.Xml.XmlReader]::Create($xmlPathInput, $settings) return $reader } } $result.RuleName | Should -Be "PSSec.Xxe" } It "Should allow secure XmlReaderSettings" { $result = RunRuleForCommand { function Read-XmlSafe { param($xmlPathInput) $settings = New-Object System.Xml.XmlReaderSettings $settings.XmlResolver = $null $settings.DtdProcessing = [System.Xml.DtdProcessing]::Prohibit $reader = [System.Xml.XmlReader]::Create($xmlPathInput, $settings) return $reader } } $result | Should -Be $null } } Describe "Tests for PSSec.Xee" { It "Should detect XML entity expansion risk with unbounded settings" { $result = RunRuleForCommand { function Read-XmlUnsafe { param($xmlInput) $settings = New-Object System.Xml.XmlReaderSettings $settings.DtdProcessing = [System.Xml.DtdProcessing]::Parse $settings.MaxCharactersFromEntities = 0 $reader = [System.Xml.XmlReader]::Create($xmlInput, $settings) return $reader } } $result.RuleName | Should -Be "PSSec.Xee" } It "Should allow bounded and non-parse settings" { $result = RunRuleForCommand { function Read-XmlSafe { param($xmlInput) $settings = New-Object System.Xml.XmlReaderSettings $settings.DtdProcessing = [System.Xml.DtdProcessing]::Prohibit $settings.MaxCharactersFromEntities = 1024 $reader = [System.Xml.XmlReader]::Create($xmlInput, $settings) return $reader } } $result | Should -Be $null } } Describe "Tests for PSSec.SessionTimeout" { It "Should detect negative timeout assignment" { $result = RunRuleForCommand { function Set-Session { $session = [PSCustomObject]@{} $session.Timeout = -1 return $session } } $result.RuleName | Should -Be "PSSec.SessionTimeout" } It "Should detect excessive timeout assignment" { $result = RunRuleForCommand { function Set-Session { $session = [PSCustomObject]@{} $session.Timeout = 120 return $session } } $result.RuleName | Should -Be "PSSec.SessionTimeout" } It "Should allow normal timeout assignment" { $result = RunRuleForCommand { function Set-Session { $session = [PSCustomObject]@{} $session.Timeout = 30 return $session } } $result | Should -Be $null } } Describe "Tests for PSSec.Ssrf" { It "Should detect tainted URL in Invoke-WebRequest" { $result = RunRuleForCommand { function Invoke-Remote { param($url) Invoke-WebRequest -Uri $url } } $result.RuleName | Should -Be "PSSec.Ssrf" } It "Should detect tainted endpoint in Invoke-RestMethod" { $result = RunRuleForCommand { function Invoke-Remote { param($endpoint) $target = "http://$endpoint/api" Invoke-RestMethod -Uri $target } } $result.RuleName | Should -Be "PSSec.Ssrf" } It "Should allow constant URL request" { $result = RunRuleForCommand { function Invoke-Remote { $uri = "https://api.example.com/status" Invoke-RestMethod -Uri $uri } } $result | Should -Be $null } } Describe "Tests for PSSec.LogInjection" { It "Should detect tainted message in Write-EventLog" { $result = RunRuleForCommand { function Write-AppEvent { param($message) Write-EventLog -LogName Application -Source Demo -EntryType Information -EventId 1 -Message $message } } $result.RuleName | Should -Be "PSSec.LogInjection" } It "Should detect tainted message in Add-Content" { $result = RunRuleForCommand { function Write-AppLog { param($userInput) $line = "user=$userInput" Add-Content -Path .\app.log -Value $line } } $result.RuleName | Should -Be "PSSec.LogInjection" } It "Should allow escaped message before logging" { $result = RunRuleForCommand { function Write-AppLog { param($message) $safe = [System.Security.SecurityElement]::Escape($message) Add-Content -Path .\app.log -Value $safe } } $result | Should -Be $null } } Describe "Tests for PSSec.LdapInjection" { It "Should detect tainted LDAP filter assignment" { $result = RunRuleForCommand { function Search-LdapUnsafe { param($userName, $password) $filter = "(&(userId=$userName)(UserPassword=$password))" $searcher = New-Object psobject $searcher.Filter = $filter } } $result.RuleName | Should -Be "PSSec.LdapInjection" } It "Should detect tainted LDAPFilter command parameter" { $result = RunRuleForCommand { function Search-LdapUnsafe2 { param($employeeName) $ldap = "(&(objectClass=user)(employeename=$employeeName))" Get-ADUser -LDAPFilter $ldap } } $result.RuleName | Should -Be "PSSec.LdapInjection" } It "Should allow escaped LDAP values" { $result = RunRuleForCommand { function Search-LdapSafe { param($userName, $password) $safeUser = [Microsoft.Security.Application.Encoder]::LdapFilterEncode($userName) $safePwd = [Microsoft.Security.Application.Encoder]::LdapFilterEncode($password) $filter = "(&(userId=$safeUser)(UserPassword=$safePwd))" $searcher = New-Object psobject $searcher.Filter = $filter } } $result | Should -Be $null } } Describe "Tests for PSSec.SensitiveErrorExposure" { It "Should detect stack trace exposure in catch" { $result = RunRuleForCommand { function Show-ErrorUnsafe { param($value) try { [int]::Parse($value) | Out-Null } catch { Write-Output $_.ScriptStackTrace } } } $result.RuleName | Should -Be "PSSec.SensitiveErrorExposure" } It "Should detect raw exception output in catch" { $result = RunRuleForCommand { function Show-ErrorUnsafe2 { param($value) try { [int]::Parse($value) | Out-Null } catch { Write-Host $_ } } } $result.RuleName | Should -Be "PSSec.SensitiveErrorExposure" } It "Should allow generic error message" { $result = RunRuleForCommand { function Show-ErrorSafe { param($value) try { [int]::Parse($value) | Out-Null } catch { Write-Output "An error has occurred." } } } $result | Should -Be $null } } Describe "Tests for PSSec.XPathInjection" { It "Should detect tainted XPath expression in Evaluate" { $result = RunRuleForCommand { function Query-XmlUnsafe { param($username, $passwordHash) $query = "//users/user[username/text()='$username' and passwordHash/text()='$passwordHash']/data/text()" $navigator = New-Object psobject $navigator.Evaluate($query) } } $result.RuleName | Should -Be "PSSec.XPathInjection" } It "Should detect tainted XPath in Select-Xml" { $result = RunRuleForCommand { function Query-XmlUnsafe2 { param($xpathExpr) Select-Xml -Path .\users.xml -XPath $xpathExpr } } $result.RuleName | Should -Be "PSSec.XPathInjection" } It "Should allow escaped XPath values" { $result = RunRuleForCommand { function Query-XmlSafe { param($username, $passwordHash) $safeUser = [System.Security.SecurityElement]::Escape($username) $safeHash = [System.Security.SecurityElement]::Escape($passwordHash) $query = "//users/user[username/text()='$safeUser' and passwordHash/text()='$safeHash']/data/text()" $navigator = New-Object psobject $navigator.Evaluate($query) } } $result | Should -Be $null } } Describe "Tests for PSSec.OpenRedirect" { It "Should detect tainted URL in Redirect" { $result = RunRuleForCommand { function Redirect-Unsafe { param($redirectUrl) $response = New-Object psobject $response.Redirect($redirectUrl) } } $result.RuleName | Should -Be "PSSec.OpenRedirect" } It "Should detect tainted URL in RedirectPermanent" { $result = RunRuleForCommand { function Redirect-Unsafe2 { param($next) $url = $next $response = New-Object psobject $response.RedirectPermanent($url) } } $result.RuleName | Should -Be "PSSec.OpenRedirect" } It "Should allow IsLocalUrl validated redirect" { $result = RunRuleForCommand { function Redirect-Safe { param($redirectUrl) if([Microsoft.AspNet.Membership.OpenAuth.OpenAuth]::IsLocalUrl($redirectUrl)) { $response = New-Object psobject $response.Redirect($redirectUrl) } } } $result | Should -Be $null } } Describe "Tests for PSSec.TaintedConfig" { It "Should detect tainted config in connection string" { $result = RunRuleForCommand { function Set-DbConfigUnsafe { param($catalog) $dbConnection = New-Object psobject $dbConnection.ConnectionString = "Data Source=.;Initial Catalog=$catalog;User ID=sa;Password=pass;" } } @($result.RuleName) | Should -Contain "PSSec.TaintedConfig" } It "Should detect tainted config command argument" { $result = RunRuleForCommand { function Set-AppConfigUnsafe { param($tenant) Set-ItemProperty -Path HKLM:\Software\Demo -Name Config -Configuration $tenant } } @($result.RuleName) | Should -Contain "PSSec.TaintedConfig" } It "Should allow allowlisted configuration value" { $result = RunRuleForCommand { function Set-DbConfigSafe { param($catalog, [System.Collections.Generic.HashSet[string]]$validCatalogNames) if(-not $validCatalogNames.Contains($catalog)) { return } $dbConnection = New-Object psobject $dbConnection.ConnectionString = "Data Source=.;Initial Catalog=$catalog;User ID=sa;Password=pass;" } } @($result).Count | Should -Be 0 } } Describe "Tests for PSSec.VulnerablePackage" { It "Should detect vulnerable Google.Protobuf version" { $result = RunRuleForCommand { $pkg = "Google.Protobuf 3.11.4" Write-Output $pkg } @($result.RuleName) | Should -Contain "PSSec.VulnerablePackage" } It "Should detect vulnerable SSH.NET version" { $result = RunRuleForCommand { $pkg = "SSH.NET 2016.1.0" Write-Output $pkg } @($result.RuleName) | Should -Contain "PSSec.VulnerablePackage" } It "Should allow non-vulnerable package version" { $result = RunRuleForCommand { $pkg = "Google.Protobuf 3.21.12" Write-Output $pkg } @($result).Count | Should -Be 0 } } Describe "Tests for PSSec.ReDoS" { It "Should detect unsafe regex with tainted input" { $result = RunRuleForCommand { function Test-ReDoSUnsafe { param($name) $pattern = '(x+)+y' [regex]::IsMatch($name, $pattern) } } @($result.RuleName) | Should -Contain "PSSec.ReDoS" } It "Should detect inline unsafe regex pattern" { $result = RunRuleForCommand { function Test-ReDoSUnsafe2 { param($line) [regex]::Match($line, '^(-?\d+)*$').Success } } @($result.RuleName) | Should -Contain "PSSec.ReDoS" } It "Should allow safe regex pattern" { $result = RunRuleForCommand { function Test-ReDoSSafe { param($line) [regex]::IsMatch($line, '^(\d{2}-\d{2}-\d{4})$') } } @($result).Count | Should -Be 0 } } Describe "Tests for PSSec.NoSqlInjection" { It "Should detect tainted NoSQL filter string" { $result = RunRuleForCommand { function Find-UserUnsafe { param($login, $password) $filter = '{"$where":"function(){return this.login==''' + $login + ''' && this.password==''' + $password + ''';}"}' $collection = New-Object psobject $collection.Find($filter) } } @($result.RuleName) | Should -Contain "PSSec.NoSqlInjection" } It "Should detect tainted NoSQL command filter" { $result = RunRuleForCommand { function Delete-Unsafe { param($count) $query = '{"$where":"function(){return this.count == ' + $count + ';}"}' Remove-MongoItem -Filter $query } } @($result.RuleName) | Should -Contain "PSSec.NoSqlInjection" } It "Should allow typed NoSQL filter object" { $result = RunRuleForCommand { function Find-UserSafe { param($login, $password) $filter = @{ login = $login; password = $password } $collection = New-Object psobject $collection.Find($filter) } } @($result).Count | Should -Be 0 } } Describe "Tests for PSSec.ZipSlip" { It "Should detect tainted entry name used in ExtractToFile path" { $result = RunRuleForCommand { function Expand-ZipUnsafe { param($destinationDirectory, $entryName) $extractPath = [System.IO.Path]::Combine($destinationDirectory, $entryName) $entry = New-Object psobject $entry.ExtractToFile($extractPath, $true) } } @($result.RuleName) | Should -Contain "PSSec.ZipSlip" } It "Should allow validated extraction full path" { $result = RunRuleForCommand { function Expand-ZipSafe { param($destinationDirectory, $entryName) $extractPath = [System.IO.Path]::Combine($destinationDirectory, $entryName) $extractFullPath = [System.IO.Path]::GetFullPath($extractPath) $destinationDirectoryFullPath = [System.IO.Path]::GetFullPath($destinationDirectory) if(-not $extractFullPath.StartsWith($destinationDirectoryFullPath)) { throw "Zip Slip" } $entry = New-Object psobject $entry.ExtractToFile($extractFullPath, $true) } } @($result).Count | Should -Be 0 } } Describe "Tests for PSSec.InvisibleCharacter" { It "Should detect zero-width space in code" { $result = RunRuleForCommand "`$value = 'ab`u{200B}cd'" @($result.RuleName) | Should -Contain "PSSec.InvisibleCharacter" } It "Should allow normal visible text" { $result = RunRuleForCommand { $value = 'abcd' Write-Output $value } @($result).Count | Should -Be 0 } } Describe "Tests for PSSec.CookieInjection" { It "Should detect tainted value used for HttpCookie" { $result = RunRuleForCommand { function Set-CookieUnsafe { param($userRole) $cookie = New-Object System.Web.HttpCookie 'role', $userRole return $cookie } } @($result.RuleName) | Should -Contain "PSSec.CookieInjection" } It "Should allow validated cookie value" { $result = RunRuleForCommand { function Set-CookieSafe { param($cultureValue) if(-not [regex]::IsMatch($cultureValue, '^[a-zA-Z0-9_-]{2,20}$')) { return } $cookie = New-Object System.Web.HttpCookie 'culture', $cultureValue return $cookie } } @($result).Count | Should -Be 0 } } Describe "Tests for PSSec.ExternallyControlledFormatString" { It "Should detect tainted format string in string.Format" { $result = RunRuleForCommand { function Apply-UnsafeFormat { param($format, $name) [string]::Format($format, $name) } } @($result.RuleName) | Should -Contain "PSSec.ExternallyControlledFormatString" } It "Should allow constant format string" { $result = RunRuleForCommand { function Apply-SafeFormat { param($name) $format = "Hello {0}" [string]::Format($format, $name) } } @($result).Count | Should -Be 0 } } Describe "Tests for PSSec.ExcessiveFilePermission" { It "Should detect overly broad chmod numeric mode" { $result = RunRuleForCommand { function Set-UnsafePermission { param($path) chmod 777 $path } } @($result.RuleName) | Should -Contain "PSSec.ExcessiveFilePermission" } It "Should allow least-privilege chmod mode" { $result = RunRuleForCommand { function Set-SafePermission { param($path) chmod 640 $path } } @($result).Count | Should -Be 0 } } Describe "Tests for PSSec.PredictableRandomSeed" { It "Should detect constant random seed" { $result = RunRuleForCommand { function Get-UnsafeNonce { $rnd = [System.Random]::new(4040) return $rnd.Next() } } @($result.RuleName) | Should -Contain "PSSec.PredictableRandomSeed" } It "Should allow cryptographic RNG usage" { $result = RunRuleForCommand { function Get-SafeNonce { $bytes = [byte[]]::new(16) [System.Security.Cryptography.RandomNumberGenerator]::Fill($bytes) return [Convert]::ToHexString($bytes) } } @($result).Count | Should -Be 0 } } Describe "Tests for PSSec.CustomCryptographicAlgorithm" { It "Should detect class extending HashAlgorithm" { $result = RunRuleForCommand { class MyCustomDigest : System.Security.Cryptography.HashAlgorithm { [void] Initialize() {} [byte[]] HashFinal() { return [byte[]]::new(0) } [void] HashCore([byte[]]$array, [int]$ibStart, [int]$cbSize) {} } } @($result.RuleName) | Should -Contain "PSSec.CustomCryptographicAlgorithm" } It "Should allow standard SHA256 usage" { $result = RunRuleForCommand { function Get-SafeHash { param([string]$input) $sha = [System.Security.Cryptography.SHA256]::Create() $bytes = [System.Text.Encoding]::UTF8.GetBytes($input) return $sha.ComputeHash($bytes) } } @($result).Count | Should -Be 0 } } Describe "Tests for PSSec.UnrestrictedPosixPermission" { It "Should detect chmod granting rights to others" { $result = RunRuleForCommand { function Set-UnsafePosixMode { param($path) chmod o+rw $path } } @($result.RuleName) | Should -Contain "PSSec.UnrestrictedPosixPermission" } It "Should allow removing others permissions" { $result = RunRuleForCommand { function Set-SafePosixMode { param($path) chmod o-rwx,u+rw $path } } @($result).Count | Should -Be 0 } } Describe "Example tests for PSSec.ExcessiveFilePermission" { It "Should detect Vulnerable_ExcessiveFilePermission_1.ps1" { $result = RunRuleForFile "Vulnerable_ExcessiveFilePermission_1.ps1" @($result.RuleName) | Should -Contain "PSSec.ExcessiveFilePermission" } It "Should detect Vulnerable_ExcessiveFilePermission_2.ps1" { $result = RunRuleForFile "Vulnerable_ExcessiveFilePermission_2.ps1" @($result.RuleName) | Should -Contain "PSSec.ExcessiveFilePermission" } It "Should allow Safe_ExcessiveFilePermission_1.ps1" { $result = RunRuleForFile "Safe_ExcessiveFilePermission_1.ps1" @($result | Where-Object RuleName -eq "PSSec.ExcessiveFilePermission").Count | Should -Be 0 } } Describe "Example tests for PSSec.PredictableRandomSeed" { It "Should detect Vulnerable_PredictableRandomSeed_1.ps1" { $result = RunRuleForFile "Vulnerable_PredictableRandomSeed_1.ps1" @($result.RuleName) | Should -Contain "PSSec.PredictableRandomSeed" } It "Should detect Vulnerable_PredictableRandomSeed_2.ps1" { $result = RunRuleForFile "Vulnerable_PredictableRandomSeed_2.ps1" @($result.RuleName) | Should -Contain "PSSec.PredictableRandomSeed" } It "Should allow Safe_PredictableRandomSeed_1.ps1" { $result = RunRuleForFile "Safe_PredictableRandomSeed_1.ps1" @($result | Where-Object RuleName -eq "PSSec.PredictableRandomSeed").Count | Should -Be 0 } } Describe "Example tests for PSSec.CustomCryptographicAlgorithm" { It "Should detect Vulnerable_CustomCryptographicAlgorithm_1.ps1" { $result = RunRuleForFile "Vulnerable_CustomCryptographicAlgorithm_1.ps1" @($result.RuleName) | Should -Contain "PSSec.CustomCryptographicAlgorithm" } It "Should detect Vulnerable_CustomCryptographicAlgorithm_2.ps1" { $result = RunRuleForFile "Vulnerable_CustomCryptographicAlgorithm_2.ps1" @($result.RuleName) | Should -Contain "PSSec.CustomCryptographicAlgorithm" } It "Should allow Safe_CustomCryptographicAlgorithm_1.ps1" { $result = RunRuleForFile "Safe_CustomCryptographicAlgorithm_1.ps1" @($result | Where-Object RuleName -eq "PSSec.CustomCryptographicAlgorithm").Count | Should -Be 0 } } Describe "Example tests for PSSec.UnrestrictedPosixPermission" { It "Should detect Vulnerable_UnrestrictedPosixPermission_1.ps1" { $result = RunRuleForFile "Vulnerable_UnrestrictedPosixPermission_1.ps1" @($result.RuleName) | Should -Contain "PSSec.UnrestrictedPosixPermission" } It "Should detect Vulnerable_UnrestrictedPosixPermission_2.ps1" { $result = RunRuleForFile "Vulnerable_UnrestrictedPosixPermission_2.ps1" @($result.RuleName) | Should -Contain "PSSec.UnrestrictedPosixPermission" } It "Should allow Safe_UnrestrictedPosixPermission_1.ps1" { $result = RunRuleForFile "Safe_UnrestrictedPosixPermission_1.ps1" @($result | Where-Object RuleName -eq "PSSec.UnrestrictedPosixPermission").Count | Should -Be 0 } } Describe "Tests for PSSec.InsecureCorsWildcardOrigin" { It "Should detect wildcard CORS header in hashtable" { $result = RunRuleForCommand { function Set-CorsUnsafe { $headers = @{ 'Access-Control-Allow-Origin' = '*' } return $headers } } @($result.RuleName) | Should -Contain "PSSec.InsecureCorsWildcardOrigin" } It "Should allow explicit CORS origin" { $result = RunRuleForCommand { function Set-CorsSafe { $headers = @{ 'Access-Control-Allow-Origin' = 'https://allowed.example.com' } return $headers } } @($result | Where-Object RuleName -eq "PSSec.InsecureCorsWildcardOrigin").Count | Should -Be 0 } } Describe "Tests for PSSec.EmptyDbPassword" { It "Should detect empty password assignment" { $result = RunRuleForCommand { function Connect-UnsafeDb { $conn = New-Object psobject $conn.Password = "" return $conn } } @($result.RuleName) | Should -Contain "PSSec.EmptyDbPassword" } It "Should allow password from environment" { $result = RunRuleForCommand { function Connect-SafeDb { $pwd = $env:DB_PASSWORD Invoke-Sqlcmd -ServerInstance 'db01' -Username 'server' -Password $pwd -Query 'SELECT 1' } } @($result | Where-Object RuleName -eq "PSSec.EmptyDbPassword").Count | Should -Be 0 } } Describe "Tests for PSSec.NonAtomicTempFileCreation" { It "Should detect delete and recreate temp path flow" { $result = RunRuleForCommand { function New-TempUnsafe { $tmp = [System.IO.Path]::GetTempFileName() Remove-Item $tmp New-Item -ItemType Directory -Path $tmp | Out-Null } } @($result.RuleName) | Should -Contain "PSSec.NonAtomicTempFileCreation" } It "Should allow direct temp directory creation" { $result = RunRuleForCommand { function New-TempSafe { $tmpDir = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.Guid]::NewGuid().ToString()) [System.IO.Directory]::CreateDirectory($tmpDir) | Out-Null } } @($result | Where-Object RuleName -eq "PSSec.NonAtomicTempFileCreation").Count | Should -Be 0 } } Describe "Tests for PSSec.HardcodedIpAddress" { It "Should detect hardcoded IPv4 literal" { $result = RunRuleForCommand { function Connect-UnsafeIp { $ip = '117.107.58.59' Test-Connection -ComputerName $ip -Count 1 } } @($result.RuleName) | Should -Contain "PSSec.HardcodedIpAddress" } It "Should allow endpoint from environment variable" { $result = RunRuleForCommand { function Connect-SafeIp { $ip = $env:APP_SERVER_IP Test-Connection -ComputerName $ip -Count 1 } } @($result | Where-Object RuleName -eq "PSSec.HardcodedIpAddress").Count | Should -Be 0 } } Describe "Example tests for PSSec.InsecureCorsWildcardOrigin" { It "Should detect Vulnerable_InsecureCorsWildcardOrigin_1.ps1" { $result = RunRuleForFile "Vulnerable_InsecureCorsWildcardOrigin_1.ps1" @($result.RuleName) | Should -Contain "PSSec.InsecureCorsWildcardOrigin" } It "Should detect Vulnerable_InsecureCorsWildcardOrigin_2.ps1" { $result = RunRuleForFile "Vulnerable_InsecureCorsWildcardOrigin_2.ps1" @($result.RuleName) | Should -Contain "PSSec.InsecureCorsWildcardOrigin" } It "Should allow Safe_InsecureCorsWildcardOrigin_1.ps1" { $result = RunRuleForFile "Safe_InsecureCorsWildcardOrigin_1.ps1" @($result | Where-Object RuleName -eq "PSSec.InsecureCorsWildcardOrigin").Count | Should -Be 0 } } Describe "Example tests for PSSec.EmptyDbPassword" { It "Should detect Vulnerable_EmptyDbPassword_1.ps1" { $result = RunRuleForFile "Vulnerable_EmptyDbPassword_1.ps1" @($result.RuleName) | Should -Contain "PSSec.EmptyDbPassword" } It "Should detect Vulnerable_EmptyDbPassword_2.ps1" { $result = RunRuleForFile "Vulnerable_EmptyDbPassword_2.ps1" @($result.RuleName) | Should -Contain "PSSec.EmptyDbPassword" } It "Should allow Safe_EmptyDbPassword_1.ps1" { $result = RunRuleForFile "Safe_EmptyDbPassword_1.ps1" @($result | Where-Object RuleName -eq "PSSec.EmptyDbPassword").Count | Should -Be 0 } } Describe "Example tests for PSSec.NonAtomicTempFileCreation" { It "Should detect Vulnerable_NonAtomicTempFileCreation_1.ps1" { $result = RunRuleForFile "Vulnerable_NonAtomicTempFileCreation_1.ps1" @($result.RuleName) | Should -Contain "PSSec.NonAtomicTempFileCreation" } It "Should detect Vulnerable_NonAtomicTempFileCreation_2.ps1" { $result = RunRuleForFile "Vulnerable_NonAtomicTempFileCreation_2.ps1" @($result.RuleName) | Should -Contain "PSSec.NonAtomicTempFileCreation" } It "Should allow Safe_NonAtomicTempFileCreation_1.ps1" { $result = RunRuleForFile "Safe_NonAtomicTempFileCreation_1.ps1" @($result | Where-Object RuleName -eq "PSSec.NonAtomicTempFileCreation").Count | Should -Be 0 } } Describe "Example tests for PSSec.HardcodedIpAddress" { It "Should detect Vulnerable_HardcodedIpAddress_1.ps1" { $result = RunRuleForFile "Vulnerable_HardcodedIpAddress_1.ps1" @($result.RuleName) | Should -Contain "PSSec.HardcodedIpAddress" } It "Should detect Vulnerable_HardcodedIpAddress_2.ps1" { $result = RunRuleForFile "Vulnerable_HardcodedIpAddress_2.ps1" @($result.RuleName) | Should -Contain "PSSec.HardcodedIpAddress" } It "Should allow Safe_HardcodedIpAddress_1.ps1" { $result = RunRuleForFile "Safe_HardcodedIpAddress_1.ps1" @($result | Where-Object RuleName -eq "PSSec.HardcodedIpAddress").Count | Should -Be 0 } } Describe "Tests for PSSec.UnencryptedCommunicationChannel" { It "Should detect HTTP endpoint in PowerShell web request" { $result = RunRuleForCommand { function Invoke-UnsafeHttp { Invoke-WebRequest -Uri 'http://internal.service.local/api' } } @($result.RuleName) | Should -Contain "PSSec.UnencryptedCommunicationChannel" } It "Should allow HTTPS endpoint" { $result = RunRuleForCommand { function Invoke-SafeHttps { Invoke-RestMethod -Uri 'https://internal.service.local/api' } } @($result | Where-Object RuleName -eq "PSSec.UnencryptedCommunicationChannel").Count | Should -Be 0 } } Describe "Tests for PSSec.SensitiveCredentialPattern" { It "Should detect GitHub token pattern" { $result = RunRuleForCommand { function Leak-GitHubToken { $token = 'ghp_0123456789abcdefghijklmnopqrstuvwxyzAB' return $token } } @($result.RuleName) | Should -Contain "PSSec.SensitiveCredentialPattern" } It "Should allow env-based token retrieval" { $result = RunRuleForCommand { function Use-SafeToken { $token = $env:API_TOKEN return $token } } @($result | Where-Object RuleName -eq "PSSec.SensitiveCredentialPattern").Count | Should -Be 0 } } Describe "Tests for PSSec.AuthenticationBypassSpoofing" { It "Should detect REMOTE_ADDR-based trust check" { $result = RunRuleForCommand { function Test-SpoofableIpAuth { if($env:REMOTE_ADDR -eq '127.0.0.1') { return $true } return $false } } @($result.RuleName) | Should -Contain "PSSec.AuthenticationBypassSpoofing" } It "Should allow auth not based on remote host headers" { $result = RunRuleForCommand { function Test-SafeAuthSignal { param($userId) return ($userId -eq 'admin') } } @($result | Where-Object RuleName -eq "PSSec.AuthenticationBypassSpoofing").Count | Should -Be 0 } } Describe "Example tests for PSSec.UnencryptedCommunicationChannel" { It "Should detect Vulnerable_UnencryptedCommunicationChannel_1.ps1" { $result = RunRuleForFile "Vulnerable_UnencryptedCommunicationChannel_1.ps1" @($result.RuleName) | Should -Contain "PSSec.UnencryptedCommunicationChannel" } It "Should detect Vulnerable_UnencryptedCommunicationChannel_2.ps1" { $result = RunRuleForFile "Vulnerable_UnencryptedCommunicationChannel_2.ps1" @($result.RuleName) | Should -Contain "PSSec.UnencryptedCommunicationChannel" } It "Should allow Safe_UnencryptedCommunicationChannel_1.ps1" { $result = RunRuleForFile "Safe_UnencryptedCommunicationChannel_1.ps1" @($result | Where-Object RuleName -eq "PSSec.UnencryptedCommunicationChannel").Count | Should -Be 0 } } Describe "Example tests for PSSec.SensitiveCredentialPattern" { It "Should detect Vulnerable_SensitiveCredentialPattern_1.ps1" { $result = RunRuleForFile "Vulnerable_SensitiveCredentialPattern_1.ps1" @($result.RuleName) | Should -Contain "PSSec.SensitiveCredentialPattern" } It "Should detect Vulnerable_SensitiveCredentialPattern_2.ps1" { $result = RunRuleForFile "Vulnerable_SensitiveCredentialPattern_2.ps1" @($result.RuleName) | Should -Contain "PSSec.SensitiveCredentialPattern" } It "Should detect Vulnerable_SensitiveCredentialPattern_3.ps1" { $result = RunRuleForFile "Vulnerable_SensitiveCredentialPattern_3.ps1" @($result.RuleName) | Should -Contain "PSSec.SensitiveCredentialPattern" } It "Should detect Vulnerable_SensitiveCredentialPattern_4.ps1" { $result = RunRuleForFile "Vulnerable_SensitiveCredentialPattern_4.ps1" @($result.RuleName) | Should -Contain "PSSec.SensitiveCredentialPattern" } It "Should detect Vulnerable_SensitiveCredentialPattern_5.ps1" { $result = RunRuleForFile "Vulnerable_SensitiveCredentialPattern_5.ps1" @($result.RuleName) | Should -Contain "PSSec.SensitiveCredentialPattern" } It "Should detect Vulnerable_SensitiveCredentialPattern_6.ps1" { $result = RunRuleForFile "Vulnerable_SensitiveCredentialPattern_6.ps1" @($result.RuleName) | Should -Contain "PSSec.SensitiveCredentialPattern" } It "Should detect Vulnerable_SensitiveCredentialPattern_7.ps1" { $result = RunRuleForFile "Vulnerable_SensitiveCredentialPattern_7.ps1" @($result.RuleName) | Should -Contain "PSSec.SensitiveCredentialPattern" } It "Should detect Vulnerable_SensitiveCredentialPattern_8.ps1" { $result = RunRuleForFile "Vulnerable_SensitiveCredentialPattern_8.ps1" @($result.RuleName) | Should -Contain "PSSec.SensitiveCredentialPattern" } It "Should allow Safe_SensitiveCredentialPattern_1.ps1" { $result = RunRuleForFile "Safe_SensitiveCredentialPattern_1.ps1" @($result | Where-Object RuleName -eq "PSSec.SensitiveCredentialPattern").Count | Should -Be 0 } } Describe "Example tests for PSSec.AuthenticationBypassSpoofing" { It "Should detect Vulnerable_AuthenticationBypassSpoofing_1.ps1" { $result = RunRuleForFile "Vulnerable_AuthenticationBypassSpoofing_1.ps1" @($result.RuleName) | Should -Contain "PSSec.AuthenticationBypassSpoofing" } It "Should detect Vulnerable_AuthenticationBypassSpoofing_2.ps1" { $result = RunRuleForFile "Vulnerable_AuthenticationBypassSpoofing_2.ps1" @($result.RuleName) | Should -Contain "PSSec.AuthenticationBypassSpoofing" } It "Should allow Safe_AuthenticationBypassSpoofing_1.ps1" { $result = RunRuleForFile "Safe_AuthenticationBypassSpoofing_1.ps1" @($result | Where-Object RuleName -eq "PSSec.AuthenticationBypassSpoofing").Count | Should -Be 0 } } |