CopilotHome.ps1
|
function Get-CopilotHome { [CmdletBinding()] [OutputType([string])] param() if (-not $HOME) { throw 'Unable to resolve the user home directory.' } return $HOME } function Resolve-CopilotSessionPath { <# .SYNOPSIS Resolves a Copilot session ID to its directory strictly under the canonical session-state root, rejecting path-traversal input. .DESCRIPTION Central identity/path guard shared by the session cmdlets. A valid session ID is a single directory name (Copilot CLI session IDs are GUIDs). The ID must not contain directory separators, be a rooted/absolute path, carry a drive or stream qualifier, or be a '.'/'..' dot segment. The resolved target is then canonicalized and confirmed to be a direct child of the session-state root, defeating any residual traversal. Throws a terminating error for unsafe input. Returns the canonical directory path when the session exists, or $null when a valid ID has no matching session directory. .PARAMETER Id The session ID to validate and resolve. #> [CmdletBinding()] [OutputType([string])] param( [Parameter(Mandatory)] [string]$Id ) if ([string]::IsNullOrWhiteSpace($Id)) { throw 'Invalid Copilot session ID: the ID must not be empty.' } # Reject anything that is not a single, safe path component before it ever # reaches Join-Path: no separators, no rooted/absolute paths, no drive or # alternate-stream qualifier, and no '.'/'..' dot segments. if ($Id -match '[\\/]' -or $Id -eq '.' -or $Id -eq '..' -or [System.IO.Path]::IsPathRooted($Id) -or $Id.IndexOf([System.IO.Path]::VolumeSeparatorChar) -ge 0) { throw "Invalid Copilot session ID '$Id': the ID must be a single session directory name without path separators, drive qualifiers, or relative segments." } $separator = [System.IO.Path]::DirectorySeparatorChar $comparison = if ($IsWindows) { [System.StringComparison]::OrdinalIgnoreCase } else { [System.StringComparison]::Ordinal } $root = Join-Path (Get-CopilotHome) '.copilot' 'session-state' $rootFull = [System.IO.Path]::GetFullPath($root + $separator) # Canonicalize the candidate and confirm it resolves to a direct child of the # session-state root. $candidateFull = [System.IO.Path]::GetFullPath((Join-Path $root $Id)) $parentFull = [System.IO.Path]::GetFullPath([System.IO.Path]::GetDirectoryName($candidateFull) + $separator) if (-not $parentFull.Equals($rootFull, $comparison)) { throw "Invalid Copilot session ID '$Id': the resolved path escapes the session-state root." } if (-not (Test-Path -LiteralPath $candidateFull -PathType Container)) { return $null } return $candidateFull } |