Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 80 additions & 34 deletions scripts/install.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,48 @@ function Main {
exit 1
}

# Validate install directory is under standard user-profile locations.
# Program Files is intentionally excluded — it requires admin privileges and
# writing there could enable DLL/EXE planting. Non-standard paths are allowed
# only with explicit interactive confirmation.
# Note: install.sh uses a different model (character-set + traversal deny-list)
# because Unix paths don't have canonical env-var-based user directories.
Comment on lines +174 to +179
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The header usage comment still shows an example installing under C:\Program Files\..., but this block now explicitly discourages/excludes Program Files and may require confirmation (or fail in non-interactive mode). Please update the usage comment/help text to reflect the new recommended install locations/behavior.

Copilot uses AI. Check for mistakes.
$allowedPrefixes = @($env:LOCALAPPDATA, $env:APPDATA, $env:USERPROFILE)
$isAllowlisted = $false
foreach ($prefix in $allowedPrefixes) {
if (-not $prefix) { continue }
# Normalize prefix the same way $InstallDir was normalized above,
# then append trailing backslash to prevent prefix confusion
# (e.g., "LocalLow" matching "Local")
$prefix = [System.IO.Path]::GetFullPath($prefix)
$normalizedPrefix = $prefix.TrimEnd('\') + '\'
if ($InstallDir.StartsWith($normalizedPrefix, [System.StringComparison]::OrdinalIgnoreCase)) {
Comment on lines +188 to +189
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Allowlist check won’t treat an install dir that is exactly equal to an allowed prefix (e.g., -InstallDir $env:LOCALAPPDATA or $env:USERPROFILE) as allowlisted because StartsWith($prefix + '\\') requires a trailing separator. Consider allowing equality as well (compare trimmed paths) so standard root locations don’t incorrectly trigger the confirmation/fail-fast path.

Suggested change
$normalizedPrefix = $prefix.TrimEnd('\') + '\'
if ($InstallDir.StartsWith($normalizedPrefix, [System.StringComparison]::OrdinalIgnoreCase)) {
$normalizedPrefixRoot = $prefix.TrimEnd('\')
$normalizedPrefix = $normalizedPrefixRoot + '\'
if (
[string]::Equals(
$InstallDir.TrimEnd('\'),
$normalizedPrefixRoot,
[System.StringComparison]::OrdinalIgnoreCase
) -or
$InstallDir.StartsWith($normalizedPrefix, [System.StringComparison]::OrdinalIgnoreCase)
) {

Copilot uses AI. Check for mistakes.
$isAllowlisted = $true
break
}
}
if (-not $isAllowlisted) {
# In non-interactive contexts (e.g., irm ... | iex), Read-Host would hang
# or return empty. Detect this and fail immediately instead.
# Wrap in try/catch because [Console]::IsInputRedirected can throw in
# some hosts (ISE, remote sessions) where console handles are unavailable.
try {
$isInteractive = [Environment]::UserInteractive -and -not [Console]::IsInputRedirected
} catch {
$isInteractive = $false
}
if (-not $isInteractive) {
Write-Error "Install directory '$InstallDir' is outside standard user locations and cannot be confirmed in non-interactive mode. Use -InstallDir with a path under `$env:LOCALAPPDATA, `$env:APPDATA, or `$env:USERPROFILE."
exit 1
}
Write-Warning "Install directory '$InstallDir' is outside standard user locations."
$confirm = Read-Host "Continue? (y/N)"
if ($confirm -ne 'y' -and $confirm -ne 'Y') {
Write-Error "Installation cancelled by user"
exit 1
}
}

# Validate Version format (except for the special 'latest' value)
if ($Version -ne "latest" -and $Version -notmatch '^v?\d+\.\d+\.\d+(-[0-9A-Za-z\.-]+)?$') {
Write-Error "Invalid version format: '$Version'. Expected formats like 'v1.2.3' or '1.2.3' (optionally with suffix like -beta.1), or 'latest'."
Expand Down Expand Up @@ -227,53 +269,57 @@ function Main {
$binarySource = Join-Path $tmpDir $BinaryName
$binaryDest = Join-Path $InstallDir $BinaryName

# Detect upgrade vs fresh install
# CWE-78: Do NOT execute the pre-existing binary for version detection —
# an attacker could have planted a malicious executable at this path.
if (Test-Path $binaryDest) {
try {
$existingVersion = & $binaryDest --version 2>$null | Select-Object -First 1
if ($existingVersion) {
Write-Host "Upgrading existing installation..."
Write-Host " Current: $existingVersion"
} else {
Write-Host "Replacing existing installation..."
}
} catch {
Write-Host "Replacing existing installation..."
}
Write-Host "Replacing existing installation..."
}

Copy-Item -Path $binarySource -Destination $binaryDest -Force

# Add to PATH (skip persistent changes in CI environments where PATH is ephemeral)
if (-not (Test-CIEnvironment)) {
$currentPath = [Environment]::GetEnvironmentVariable("Path", "User")
$newUserPath = Add-DirectoryToPath -ExistingPath $currentPath -Directory $InstallDir
if ($newUserPath -ne $currentPath) {
Write-Host "Adding to PATH..."
[Environment]::SetEnvironmentVariable(
"Path",
$newUserPath,
"User"
)
# CWE-426: Only modify PATH for allowlisted directories. Non-standard
# paths (confirmed interactively) require the user to add them manually,
# preventing an attacker-influenced directory from being added to the
# search path.
if ($isAllowlisted) {
# Add to PATH (skip persistent changes in CI environments where PATH is ephemeral)
if (-not (Test-CIEnvironment)) {
$currentPath = [Environment]::GetEnvironmentVariable("Path", "User")
$newUserPath = Add-DirectoryToPath -ExistingPath $currentPath -Directory $InstallDir
if ($newUserPath -ne $currentPath) {
Write-Host "Adding to PATH..."
[Environment]::SetEnvironmentVariable(
"Path",
$newUserPath,
"User"
)
$env:Path = Add-DirectoryToPath -ExistingPath $env:Path -Directory $InstallDir
Write-Host "Added $InstallDir to user PATH" -ForegroundColor Green
Write-Host " (Restart your terminal for PATH changes to take effect)"
}
} else {
$env:Path = Add-DirectoryToPath -ExistingPath $env:Path -Directory $InstallDir
Write-Host "Added $InstallDir to user PATH" -ForegroundColor Green
Write-Host " (Restart your terminal for PATH changes to take effect)"
}
} else {
$env:Path = Add-DirectoryToPath -ExistingPath $env:Path -Directory $InstallDir
Write-Host " Note: '$InstallDir' was not added to PATH automatically (non-standard location)."
Write-Host " Add it manually if needed: `$env:Path += `";$InstallDir`""
}

Write-Host ""
Write-Host "Armis CLI installed successfully!" -ForegroundColor Green

# Show installed version
try {
$newVersion = & $binaryDest --version 2>$null | Select-Object -First 1
if ($newVersion) {
Write-Host " Location: $binaryDest"
Write-Host " Version: $newVersion"
}
} catch {}
Write-Host " Location: $binaryDest"
# CWE-78: Only execute the installed binary for version display when
# integrity verification was performed (checksums validated). Without
# verification, we cannot trust the binary content.
if ($Verify) {
try {
$newVersion = & $binaryDest --version 2>$null | Select-Object -First 1
if ($newVersion) {
Write-Host " Version: $newVersion"
}
} catch {}
}

Write-Host ""
Write-Host "Run 'armis-cli --help' to get started"
Expand Down
Loading
Loading