Private/Resolve-KadenContainedPath.ps1
|
function Resolve-KadenContainedPath { <# .SYNOPSIS Resolve a relative managed path and reject escapes outside a root. #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$Root, [Parameter(Mandatory = $true)] [AllowEmptyString()] [string]$RelativePath ) if ([string]::IsNullOrWhiteSpace($RelativePath)) { throw 'KDN-TXN-PATH-ESCAPE: managed path is empty.' } if ([System.IO.Path]::IsPathRooted($RelativePath)) { throw "KDN-TXN-PATH-ESCAPE: managed path must stay relative ('$RelativePath')." } foreach ($segment in ($RelativePath -split '[\\/]')) { if ($segment -eq '..') { throw "KDN-TXN-PATH-ESCAPE: managed path escapes its root ('$RelativePath')." } } $rootFull = [System.IO.Path]::GetFullPath($Root) $candidate = [System.IO.Path]::GetFullPath((Join-Path $rootFull $RelativePath)) $final = Resolve-KadenFinalPath -Path $candidate foreach ($check in @($candidate, $final)) { $separator = [System.IO.Path]::DirectorySeparatorChar $rootPrefix = $rootFull.TrimEnd([char]'\', [char]'/') + $separator $inside = $check.StartsWith($rootPrefix, [System.StringComparison]::OrdinalIgnoreCase) ` -or $check.Equals($rootFull, [System.StringComparison]::OrdinalIgnoreCase) if (-not $inside) { throw "KDN-TXN-PATH-ESCAPE: managed path escapes its root ('$RelativePath')." } } return $candidate } |