chore(statusline): add PowerShell caveman-statusline hook
Windows/cross-platform statusline hook for caveman mode indicator. Reads .caveman-active flag with symlink and size guards to prevent injection via crafted flag files. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
b59cce1d1f
commit
2b61276dfd
61
hooks/caveman-statusline.ps1
Normal file
61
hooks/caveman-statusline.ps1
Normal file
@ -0,0 +1,61 @@
|
||||
$ClaudeDir = if ($env:CLAUDE_CONFIG_DIR) { $env:CLAUDE_CONFIG_DIR } else { Join-Path $HOME ".claude" }
|
||||
$Flag = Join-Path $ClaudeDir ".caveman-active"
|
||||
if (-not (Test-Path $Flag)) { exit 0 }
|
||||
|
||||
# Refuse reparse points (symlinks / junctions) and oversized files. Without
|
||||
# this, a local attacker could point the flag at a secret file and have the
|
||||
# statusline render its bytes (including ANSI escape sequences) to the terminal
|
||||
# every keystroke.
|
||||
try {
|
||||
$Item = Get-Item -LiteralPath $Flag -Force -ErrorAction Stop
|
||||
if ($Item.Attributes -band [System.IO.FileAttributes]::ReparsePoint) { exit 0 }
|
||||
if ($Item.Length -gt 64) { exit 0 }
|
||||
} catch {
|
||||
exit 0
|
||||
}
|
||||
|
||||
$Mode = ""
|
||||
try {
|
||||
$Raw = Get-Content -LiteralPath $Flag -TotalCount 1 -ErrorAction Stop
|
||||
if ($null -ne $Raw) { $Mode = ([string]$Raw).Trim() }
|
||||
} catch {
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Strip anything outside [a-z0-9-] — blocks terminal-escape and OSC hyperlink
|
||||
# injection via the flag contents. Then whitelist-validate.
|
||||
$Mode = $Mode.ToLowerInvariant()
|
||||
$Mode = ($Mode -replace '[^a-z0-9-]', '')
|
||||
|
||||
$Valid = @('off','lite','full','ultra','wenyan-lite','wenyan','wenyan-full','wenyan-ultra','commit','review','compress')
|
||||
if (-not ($Valid -contains $Mode)) { exit 0 }
|
||||
|
||||
$Esc = [char]27
|
||||
if ([string]::IsNullOrEmpty($Mode) -or $Mode -eq "full") {
|
||||
[Console]::Write("${Esc}[38;5;172m[CAVEMAN]${Esc}[0m")
|
||||
} else {
|
||||
$Suffix = $Mode.ToUpperInvariant()
|
||||
[Console]::Write("${Esc}[38;5;172m[CAVEMAN:$Suffix]${Esc}[0m")
|
||||
}
|
||||
|
||||
# Savings suffix: on by default. Opt out via CAVEMAN_STATUSLINE_SAVINGS=0.
|
||||
# Reads a pre-rendered string written by caveman-stats.js. Refuses reparse
|
||||
# points and strips control bytes (matches statusline.sh hardening). Until
|
||||
# /caveman-stats has run at least once, the suffix file is absent and nothing
|
||||
# is rendered — safe default for fresh installs.
|
||||
if ($env:CAVEMAN_STATUSLINE_SAVINGS -ne "0") {
|
||||
$SavingsFile = Join-Path $ClaudeDir ".caveman-statusline-suffix"
|
||||
if (Test-Path $SavingsFile) {
|
||||
try {
|
||||
$SavingsItem = Get-Item -LiteralPath $SavingsFile -Force -ErrorAction Stop
|
||||
if (-not ($SavingsItem.Attributes -band [System.IO.FileAttributes]::ReparsePoint) -and
|
||||
$SavingsItem.Length -le 64) {
|
||||
$Savings = (Get-Content -LiteralPath $SavingsFile -Raw -ErrorAction Stop).TrimEnd()
|
||||
$Savings = ($Savings -replace '[\x00-\x1F]', '')
|
||||
if ($Savings.Length -gt 0) {
|
||||
[Console]::Write(" ${Esc}[38;5;172m$Savings${Esc}[0m")
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user