# claude-key-helper.ps1 # # Invoked by Claude Code via the `apiKeyHelper` setting. Prints the API key for # the currently active preset to stdout and nothing else. # # Only presets whose auth mode is "vault" have a secret to emit. In anthropic # mode, or for a preset using an inline placeholder token (LM Studio), this # exits silently so no token can leak into a context that must not have one. # # The key is stored DPAPI-encrypted (CurrentUser scope), so decryption only # succeeds for the Windows account that ran `claude-mode set-key`. Reading the # .cred file as another user - or copying it to another machine - yields nothing. $ErrorActionPreference = 'Stop' # Claude Code surfaces a failing helper as "your apiKeyHelper script is failing" # and shows this stream under /status. Exiting 1 in silence turns four different # faults into one indistinguishable message, so every failure path says which it # was. Success paths stay silent - stdout carries the key and nothing else. function Fail { param([string] $Message) [Console]::Error.WriteLine("claude-key-helper: $Message") exit 1 } try { $root = Join-Path $env:USERPROFILE '.claude-mode' $statePath = Join-Path $root 'state.json' if (-not (Test-Path -LiteralPath $statePath)) { Fail "no state.json at $statePath - claude-mode is not installed for this Windows account. Run install.ps1" } $state = Get-Content -LiteralPath $statePath -Raw -Encoding UTF8 | ConvertFrom-Json if ($state.mode -eq 'anthropic') { exit 0 } if (-not $state.preset) { exit 0 } $presetPath = Join-Path $root ('presets\' + $state.preset + '.json') if (-not (Test-Path -LiteralPath $presetPath)) { Fail "state.json names preset '$($state.preset)' but $presetPath does not exist. Run: claude-mode presets" } $preset = Get-Content -LiteralPath $presetPath -Raw -Encoding UTF8 | ConvertFrom-Json $authMode = 'vault' $keyRef = 'openrouter' if ($preset.PSObject.Properties.Name -contains 'auth' -and $preset.auth) { if ($preset.auth.PSObject.Properties.Name -contains 'mode' -and $preset.auth.mode) { $authMode = [string]$preset.auth.mode } if ($preset.auth.PSObject.Properties.Name -contains 'keyRef' -and $preset.auth.keyRef) { $keyRef = [string]$preset.auth.keyRef } } if ($authMode -ne 'vault') { exit 0 } # inline token: nothing for us to emit $credPath = Join-Path $root ('vault\' + $keyRef + '.cred') if (-not (Test-Path -LiteralPath $credPath)) { Fail "no key stored for ref '$keyRef'. Run: claude-mode set-key $keyRef" } try { $secure = ConvertTo-SecureString (Get-Content -LiteralPath $credPath -Raw).Trim() } catch { # DPAPI is CurrentUser scope, so this is almost always a key stored by a # different Windows account than the one Claude Code is running as - # typically set-key run from an elevated or "run as" shell. Fail "cannot decrypt $credPath as $env:USERDOMAIN\$env:USERNAME. The key is DPAPI-encrypted for whichever account stored it; re-run 'claude-mode set-key $keyRef' as this user, unelevated." } $bstr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($secure) try { [Console]::Out.Write([Runtime.InteropServices.Marshal]::PtrToStringBSTR($bstr)) } finally { [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr) } } catch { Fail $_.Exception.Message }