| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142 |
- <#
- .SYNOPSIS
- Install a specific version of portless globally with verification.
- .DESCRIPTION
- 1. Inspects the published npm tarball BEFORE installing.
- 2. Verifies tarball SHA-512 against the npm registry.
- 3. Scans the package contents for known IOC strings from recent npm
- supply-chain attacks (TanStack, mini-shai-hulud).
- 4. Confirms no install scripts (preinstall/postinstall) are present.
- 5. Installs globally via npm.
- 6. Verifies version matches what was requested.
- 7. Records the pinned version.
- .PARAMETER Version
- Specific version to install (e.g. "0.13.0"). Required.
- .PARAMETER TargetDir
- Where to record version metadata (a bin/PORTLESS_VERSION file). Optional.
- .EXAMPLE
- .\install-portless.ps1 -Version 0.13.0 -TargetDir X:\my-stack
- #>
- [CmdletBinding()]
- param(
- [Parameter(Mandatory)][string]$Version,
- [string]$TargetDir = $null
- )
- $ErrorActionPreference = 'Stop'
- Write-Host "Installing portless@$Version with pre-install audit" -ForegroundColor Cyan
- Write-Host ("=" * 60)
- Write-Host ""
- # Step 1 — Inspect tarball without installing
- $tmp = Join-Path $env:TEMP "portless-inspect-$(Get-Random)"
- New-Item -ItemType Directory -Force -Path $tmp | Out-Null
- try {
- Write-Host "[1/6] Downloading tarball..." -ForegroundColor Yellow
- $tarballUrl = "https://registry.npmjs.org/portless/-/portless-$Version.tgz"
- $tarball = Join-Path $tmp 'portless.tgz'
- Invoke-WebRequest -Uri $tarballUrl -OutFile $tarball
- Write-Host "[2/6] Verifying SHA-512 against npm registry..." -ForegroundColor Yellow
- $meta = npm view portless@$Version --json 2>$null | ConvertFrom-Json
- $expectedIntegrity = $meta.dist.integrity # like "sha512-..."
- if (-not $expectedIntegrity) {
- throw "Could not fetch published integrity hash for portless@$Version"
- }
- # npm's integrity is "sha512-<base64>". Compare with our computed value.
- $actualBytes = [System.Security.Cryptography.SHA512]::Create().ComputeHash([IO.File]::ReadAllBytes($tarball))
- $actualB64 = [Convert]::ToBase64String($actualBytes)
- $expectedB64 = $expectedIntegrity -replace '^sha512-', ''
- if ($actualB64 -ne $expectedB64) {
- throw "TARBALL SHA-512 MISMATCH - aborting. Expected: $expectedB64. Got: $actualB64."
- }
- Write-Host " Match: sha512-$actualB64" -ForegroundColor Green
- Write-Host "[3/6] Extracting + auditing contents..." -ForegroundColor Yellow
- Push-Location $tmp
- tar -xzf portless.tgz
- Pop-Location
- $pkgDir = Join-Path $tmp 'package'
- # Scan for install scripts in package.json
- $pkgJson = Get-Content (Join-Path $pkgDir 'package.json') | ConvertFrom-Json
- $scripts = $pkgJson.scripts.PSObject.Properties.Name
- $installScripts = @('preinstall', 'install', 'postinstall', 'prepare')
- $foundInstallScripts = $scripts | Where-Object { $_ -in $installScripts }
- if ($foundInstallScripts) {
- Write-Warning "Package has install scripts: $($foundInstallScripts -join ', ')"
- Write-Warning "Review these before installing!"
- } else {
- Write-Host " ✓ No install scripts (preinstall/install/postinstall/prepare)"
- }
- # Check runtime dependencies (should be empty for portless)
- if ($pkgJson.dependencies -and $pkgJson.dependencies.PSObject.Properties.Count -gt 0) {
- Write-Warning "Package has runtime deps: $($pkgJson.dependencies.PSObject.Properties.Name -join ', ')"
- } else {
- Write-Host " ✓ Zero runtime dependencies"
- }
- # Scan for known IOC strings from recent attacks
- Write-Host "[4/6] Scanning for known supply-chain IOC strings..." -ForegroundColor Yellow
- $iocPatterns = @(
- 'getsession.org', 'masscan.cloud', 'git-tanstack',
- 'router_init', 'router_runtime',
- 'EveryBoiWeBuildIsAWormyBoi',
- 'claude@users.noreply.github.com',
- 'filev2.getsession',
- '@tanstack/setup',
- 'gh-token-monitor',
- '/proc/self/environ', '.claude/settings.json'
- )
- $hits = @()
- Get-ChildItem -Recurse -File $pkgDir | ForEach-Object {
- $content = Get-Content $_.FullName -Raw -ErrorAction SilentlyContinue
- foreach ($pattern in $iocPatterns) {
- if ($content -and $content.Contains($pattern)) {
- $hits += "$($_.Name): $pattern"
- }
- }
- }
- if ($hits) {
- throw "IOC MATCH FOUND - aborting:`n$($hits -join "`n")"
- }
- Write-Host " ✓ Zero IOC matches"
- # Step 5 — Install via npm
- Write-Host "[5/6] npm install -g portless@$Version..." -ForegroundColor Yellow
- npm install -g portless@$Version 2>&1 | Tee-Object -Variable npmOutput | Out-Host
- # Step 6 — Verify installed version
- Write-Host "[6/6] Verifying installed version..." -ForegroundColor Yellow
- $installed = (portless --version).Trim()
- if ($installed -ne $Version) {
- throw "Version mismatch: requested $Version, installed $installed"
- }
- Write-Host " Installed: $installed" -ForegroundColor Green
- # Optional: record in target dir
- if ($TargetDir) {
- $binDir = Join-Path $TargetDir 'bin'
- New-Item -ItemType Directory -Force -Path $binDir | Out-Null
- $Version | Out-File -FilePath (Join-Path $binDir 'PORTLESS_VERSION') -NoNewline -Encoding ascii
- Write-Host " Recorded version in: $binDir\PORTLESS_VERSION"
- }
- Write-Host ""
- Write-Host "Done. portless@$Version installed and audited." -ForegroundColor Green
- } finally {
- Remove-Item -Recurse -Force $tmp -ErrorAction SilentlyContinue
- }
|