v0.9.1: on upgrade to broker-mode, WIPE the now-obsolete local credentials

An install switching TO broker-mode (the v0.9.0 default) carried long-lived
Anthropic/OAuth credentials from the pre-broker era. Broker-mode authenticates
via short-lived broker tokens and never uses them — they are a pure security
liability on the box, acutely so on a PHI box. On the next self-update the agent
now cleans them up automatically:

- Secure-deletes $LARRY_HOME/.api-key and .oauth.json (reuses the
  uninstall-larry.sh shred -u -z -n3 -> overwrite -> rm logic).
- Strips the ANTHROPIC_API_KEY / CLAUDE_CODE_OAUTH_TOKEN LINES from
  $LARRY_HOME/.env and from ~/.bashrc, ~/.bash_profile, ~/.profile (backup
  first); every other line is kept.
- Idempotent (.broker-cred-wiped marker, written only after a run that removed
  something); silent no-op when clean.
- Hard-guarded on LARRY_AUTH_MODE=broker: does NOT fire under the apikey escape
  hatch (which legitimately still needs the key). Only the two Anthropic/OAuth
  vars are touched (LARRY_* / GITEA_TOKEN are still needed in broker mode).
- Prints a reminder to ALSO revoke at the source (local deletion != server
  revocation), per the decommission / kill-switch docs.

Fires at the broker-resolution block (after self_update synced a fresh
lib/broker.sh, before the fail-closed preflight). New functions in
lib/broker.sh: _broker_wipe_obsolete_credentials,
_broker_strip_cred_lines_from_env, _broker_strip_cred_lines_from_rc.
VERSION + MANIFEST regenerated. Tested: 31/31 assertions pass across the
upgrade-wipe, apikey-non-wipe, clean-no-op, idempotency, dangerous-path-guard,
and selective-line-strip paths.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
bj 2026-05-31 23:42:11 -07:00
parent ea9f4c2399
commit 2b578f5058
5 changed files with 198 additions and 6 deletions

View File

@ -4,6 +4,42 @@ All notable changes to `cloverleaf-larry` / `larry-anywhere` are recorded here.
Versioning is loose-semver; bumps trigger the in-process self-update on every
running client via `LARRY_BASE_URL` + `MANIFEST`.
## v0.9.1 — 2026-05-31
**Upgrade to broker-mode now WIPES the now-obsolete local Anthropic/OAuth
credentials it carried from the pre-broker era.**
When an existing install self-updates and `LARRY_AUTH_MODE` resolves to `broker`
(the v0.9.0 default), the long-lived credentials baked in before the broker
pivot are unused by broker-mode (which authenticates via short-lived broker
tokens) and are a pure security liability on the box — acutely so on a PHI box.
On the next self-update the agent now cleans them up automatically:
- **Secure-deletes** `$LARRY_HOME/.api-key` and `$LARRY_HOME/.oauth.json`
(reuses the `uninstall-larry.sh` `shred -u -z -n3` → overwrite → rm logic via
`lib/broker.sh` `_broker_secure_delete`, since on a PHI box these are sensitive).
- **Strips the credential LINES** (`ANTHROPIC_API_KEY` / `CLAUDE_CODE_OAUTH_TOKEN`,
with optional `export`/leading whitespace) from `$LARRY_HOME/.env` and from
`~/.bashrc`, `~/.bash_profile`, `~/.profile` — keeping every other line; a
timestamped `.broker-upgrade.<ts>.bak` is written before any rc/.env rewrite.
- Logs clearly what was wiped, file by file, with the secure-delete method used.
- **Idempotent:** writes `$LARRY_HOME/.broker-cred-wiped` only after a run that
actually removed something, so the rc-rewrite path isn't re-attempted every
launch; if nothing obsolete is present it no-ops silently and leaves no marker
(so a later `apikey`→`broker` flip still triggers cleanup).
- **Does NOT fire when `LARRY_AUTH_MODE=apikey`** — the escape hatch legitimately
still needs its key. Hard-guarded on `LARRY_AUTH_MODE=broker` inside the
function; only the two Anthropic/OAuth vars are touched (broker-mode still uses
`LARRY_*` and `GITEA_TOKEN`, so those rc lines are left intact).
- Prints a reminder that the operator must ALSO revoke the keys at the source
(local deletion ≠ server revocation), consistent with the decommission /
kill-switch docs.
Fires in `larry.sh` at the broker-resolution block (after `self_update` synced a
fresh `lib/broker.sh`, before the fail-closed preflight). New functions in
`lib/broker.sh`: `_broker_wipe_obsolete_credentials`,
`_broker_strip_cred_lines_from_env`, `_broker_strip_cred_lines_from_rc`.
## v0.9.0 — 2026-05-31
**Broker mode is the DEFAULT — the remote kill-switch is wired into every

View File

@ -23,7 +23,7 @@
# scripts/make-manifest.sh and bump VERSION.
# Top-level scripts
larry.sh 55c6fb42f09c923e6a8f5a8dfa9dbdd8fb5a6012dd5ac6707f87e853abcec234
larry.sh 25d81d239b268635bff6704b55f473dc795af844b7a77cf6b24ba7e214c25786
larry-tunnel.sh 6b050e4eeab15669f4858eaf3b807f168f211ced07815db9521bc40a093f6aaa
larry-auth.sh a220cdf7878569dc3028951ee57fc8d5e706a8ca5c6aa45347b58facb386f831
larry-rollback.sh 91b5e9aa6c79266bf306dcfba4ca791c07971bd6924d67a779037531648aa6d0
@ -31,9 +31,9 @@ install-larry.sh 072a036ad5bbf80e866cfd2dd74de50f8defd69a3f835032579b0cb9d421ad5
uninstall-larry.sh c53ad2d8354c7adeb243b541f027f3f481e4a8661eecfd7af14d7ca53cfcaad9
# Metadata
VERSION 9d8c94f1ad3ea96b1e2ac4914fda4cb93c76b4a3e0d8cc6dd8976d6c0b227d15
VERSION 179a5390966e85c2071a87c1b31de13df67460665196f6529f8c4986842f81e5
MANUAL.md 5ff54d6d5fae826f8b3da1eb3be6476076bb15f9b1417a4de285e59ea37e1b1f
CHANGELOG.md b3c48567452b4302b1167a3a9fb11f02964312a2f03ec393f8d35c9a1d392d90
CHANGELOG.md 934007dc1b08b6c90120f009e3cc7870815e7b251fdf8f6629aa4c004c866017
# Agent personas (system-prompt overlays)
agents/larry.md 0a1ef737e7fc133ab35be09f79c3a4df33de814e0404b69b950932d0c8a01be1
@ -55,7 +55,7 @@ lib/oauth.sh 04a93376f88fe53cc1c86a5dbe577735c60375dadd4f2fda55b921ef3cddf22b
# v0.9.0: broker client — the remote kill-switch (default rail). Enroll+mint a
# short-lived token, route LLM calls through the broker, fail-closed heartbeat,
# best-effort PHI wipe on disable.
lib/broker.sh fe05f7b349d68239b3683b1f8c3139358f586cb583c907638483f1d3389959c4
lib/broker.sh 8ec200439329ca0f93003d0976589f427de29450bbc8afab0fe32838e862bdce
# Secure SSH with ControlMaster (password hidden from Larry-the-LLM)
lib/ssh-helper.sh 18df1f1f1936c930ba0197c0e0b4bd89c027500de99b56067b620ca9144f6e9e

View File

@ -1 +1 @@
0.9.0
0.9.1

View File

@ -99,7 +99,7 @@ set -o pipefail
# ─────────────────────────────────────────────────────────────────────────────
# Config
# ─────────────────────────────────────────────────────────────────────────────
LARRY_VERSION="0.9.0"
LARRY_VERSION="0.9.1"
LARRY_HOME="${LARRY_HOME:-$HOME/.larry}"
# ─────────────────────────────────────────────────────────────────────────────
@ -1733,6 +1733,17 @@ if [ "$LARRY_AUTH_MODE" = "broker" ] && [ "$LARRY_NO_API" != "1" ]; then
exit 1
fi
# UPGRADE-TO-BROKER CREDENTIAL CLEANUP (v0.9.1): an install switching TO broker
# mode carried long-lived Anthropic/OAuth credentials from the pre-broker era.
# Broker mode never uses them (it mints short-lived broker tokens) — they're a
# pure security liability on the box, acutely so on a PHI box. Secure-delete
# .api-key/.oauth.json and strip the key/oauth LINES from .env + the shell rc
# files, keeping everything else. Idempotent (no-ops once clean); fires ONLY in
# broker mode (the apikey escape hatch legitimately still needs its key, so this
# is guarded inside the function on LARRY_AUTH_MODE=broker). Prints a reminder to
# ALSO revoke at the source. See lib/broker.sh _broker_wipe_obsolete_credentials.
_broker_wipe_obsolete_credentials
# Resolve a deployment id: explicit env wins; else derive a stable per-box id so
# "install larry on a box" auto-enrolls under a named deployment in the dashboard.
if [ -z "$LARRY_DEPLOYMENT_ID" ]; then

View File

@ -327,6 +327,151 @@ _broker_phi_wipe() {
return 0
}
# ── Upgrade-to-broker credential cleanup (v0.9.1) ────────────────────────────
# When an existing install switches TO broker-mode, the long-lived Anthropic
# credentials it carried from the pre-broker (apikey/oauth) era are UNUSED by
# broker-mode (which authenticates via short-lived broker tokens) and are a pure
# security liability on the box — most acutely on a PHI box. This wipes them.
#
# WHAT IT WIPES (only when LARRY_AUTH_MODE resolved to "broker"):
# - $LARRY_HOME/.api-key — secure-deleted (it's a whole-file secret)
# - $LARRY_HOME/.oauth.json — secure-deleted (whole-file OAuth refresh token)
# - $LARRY_HOME/.env — the ANTHROPIC_API_KEY / CLAUDE_CODE_OAUTH_TOKEN
# LINES are stripped; the rest of .env is KEPT.
# - ~/.bashrc, ~/.bash_profile, ~/.profile — exported ANTHROPIC_API_KEY /
# CLAUDE_CODE_OAUTH_TOKEN lines stripped (backup
# first); other lines kept.
#
# IDEMPOTENT: a marker ($LARRY_HOME/.broker-cred-wiped) is written after a run
# that found something, so the (potentially destructive) rc-rewrite path is not
# re-attempted every launch. If NOTHING obsolete is present it no-ops silently
# and does NOT write the marker (so a later apikey→broker flip still cleans up).
#
# GUARD: the caller MUST only invoke this when LARRY_AUTH_MODE is "broker". In
# apikey mode the key is legitimately still needed — do NOT call this there.
#
# NOT a substitute for revocation: local deletion ≠ server revocation. It prints
# the same "revoke at the source" reminder as uninstall-larry.sh / the
# decommission checklist.
# _broker_strip_cred_lines_from_env FILE — remove only the ANTHROPIC_API_KEY and
# CLAUDE_CODE_OAUTH_TOKEN assignment/export lines from a key=value .env, keeping
# every other line. Backs up first. Echoes "stripped" | "none" | "FAILED".
# Matches: ANTHROPIC_API_KEY=… , export ANTHROPIC_API_KEY=… , and the OAuth twin
# (with optional leading whitespace), never a substring of another var name.
_broker_strip_cred_lines_from_env() {
local f="$1"
[ -f "$f" ] || { echo "absent"; return 0; }
local re='^[[:space:]]*(export[[:space:]]+)?(ANTHROPIC_API_KEY|CLAUDE_CODE_OAUTH_TOKEN)='
grep -qE "$re" "$f" 2>/dev/null || { echo "none"; return 0; }
local ts; ts="$(date +%Y%m%d-%H%M%S 2>/dev/null || echo bak)"
cp -p "$f" "$f.broker-upgrade.$ts.bak" 2>/dev/null \
|| { _broker_log_err "could not back up $f — leaving it UNCHANGED for safety"; echo "FAILED"; return 1; }
if grep -vE "$re" "$f" > "$f.broker-tmp" 2>/dev/null && mv "$f.broker-tmp" "$f" 2>/dev/null; then
echo "stripped"; return 0
fi
rm -f "$f.broker-tmp" 2>/dev/null || true
_broker_log_err "could not rewrite $f — remove the credential line(s) by hand"
echo "FAILED"; return 1
}
# _broker_strip_cred_lines_from_rc FILE — same idea for a shell profile: strip
# only the exported ANTHROPIC_API_KEY / CLAUDE_CODE_OAUTH_TOKEN lines, keep the
# rest, backup first. (Mirrors uninstall-larry.sh's rc-strip, but scoped to the
# two Anthropic/OAuth vars only — broker-mode still legitimately uses LARRY_* and
# GITEA_TOKEN, so we must NOT strip those here.) Echoes stripped|none|FAILED.
_broker_strip_cred_lines_from_rc() {
local f="$1"
[ -f "$f" ] || { echo "absent"; return 0; }
local re='^[[:space:]]*(export[[:space:]]+)?(ANTHROPIC_API_KEY|CLAUDE_CODE_OAUTH_TOKEN)='
grep -qE "$re" "$f" 2>/dev/null || { echo "none"; return 0; }
local ts; ts="$(date +%Y%m%d-%H%M%S 2>/dev/null || echo bak)"
cp -p "$f" "$f.broker-upgrade.$ts.bak" 2>/dev/null \
|| { _broker_log_err "could not back up $f — leaving it UNCHANGED for safety"; echo "FAILED"; return 1; }
if grep -vE "$re" "$f" > "$f.broker-tmp" 2>/dev/null && mv "$f.broker-tmp" "$f" 2>/dev/null; then
echo "stripped"; return 0
fi
rm -f "$f.broker-tmp" 2>/dev/null || true
_broker_log_err "could not rewrite $f — remove the credential line(s) by hand"
echo "FAILED"; return 1
}
# _broker_wipe_obsolete_credentials — the upgrade-to-broker cleanup entry point.
# Call ONLY when LARRY_AUTH_MODE == "broker". Secure-deletes the whole-file
# secrets and strips the key/oauth lines from .env + the shell rc files. No-ops
# (and prints nothing) when there is nothing obsolete to remove.
_broker_wipe_obsolete_credentials() {
[ "${LARRY_AUTH_MODE:-}" = "broker" ] || return 0 # hard guard: broker only
local lh="${LARRY_HOME:-$HOME/.larry}"
# Same dangerous-path guard family as _broker_phi_wipe (don't operate on / etc).
local norm; norm="$(printf '%s' "$lh" | sed 's:/*$::')"
case "$norm" in
""|"/"|"/root"|"/home"|"/Users"|"/usr"|"/etc"|"/var"|"/bin"|"/tmp")
_broker_log_err "credential cleanup skipped: LARRY_HOME ('$lh') resolves to a dangerous path."; return 1 ;;
esac
# Detect whether any obsolete credential is actually present. If not, no-op and
# leave NO marker (so a later apikey→broker flip still triggers cleanup).
local rc_re='^[[:space:]]*(export[[:space:]]+)?(ANTHROPIC_API_KEY|CLAUDE_CODE_OAUTH_TOKEN)='
local present=0 rc
[ -f "$lh/.api-key" ] && present=1
[ -f "$lh/.oauth.json" ] && present=1
[ -f "$lh/.env" ] && grep -qE "$rc_re" "$lh/.env" 2>/dev/null && present=1
for rc in "${HOME:-}/.bashrc" "${HOME:-}/.bash_profile" "${HOME:-}/.profile"; do
[ -n "$rc" ] && [ -f "$rc" ] && grep -qE "$rc_re" "$rc" 2>/dev/null && { present=1; break; }
done
[ "$present" = "1" ] || return 0
_broker_log_err "── upgrade to broker-mode: wiping now-obsolete local Anthropic/OAuth credentials ──"
_broker_log_err " broker-mode authenticates via short-lived broker tokens; these baked credentials"
_broker_log_err " are unused and a security liability on this box. Removing them now."
local m
# 1. Whole-file secrets — secure-delete (reuses uninstall-larry.sh shred logic).
if [ -f "$lh/.api-key" ]; then
m="$(_broker_secure_delete "$lh/.api-key")"
_broker_log_err " .api-key: $m"
fi
if [ -f "$lh/.oauth.json" ]; then
m="$(_broker_secure_delete "$lh/.oauth.json")"
_broker_log_err " .oauth.json: $m"
fi
# 2. .env — strip only the two cred LINES, keep the rest of the file.
if [ -f "$lh/.env" ]; then
m="$(_broker_strip_cred_lines_from_env "$lh/.env")"
case "$m" in
stripped) _broker_log_err " .env: stripped ANTHROPIC_API_KEY/CLAUDE_CODE_OAUTH_TOKEN line(s) (rest kept; .bak written)" ;;
none) : ;;
*) _broker_log_err " .env: $m" ;;
esac
fi
# 3. Shell profiles — strip exported key/oauth lines (backup first), keep rest.
for rc in "${HOME:-}/.bashrc" "${HOME:-}/.bash_profile" "${HOME:-}/.profile"; do
[ -n "$rc" ] || continue
m="$(_broker_strip_cred_lines_from_rc "$rc")"
case "$m" in
stripped) _broker_log_err " $rc: stripped exported key/oauth line(s) (rest kept; .bak written)" ;;
none|absent) : ;;
*) _broker_log_err " $rc: $m" ;;
esac
done
# 4. Idempotency marker — written only because we found+acted on something.
: > "$lh/.broker-cred-wiped" 2>/dev/null || true
date -u +%Y-%m-%dT%H:%M:%SZ > "$lh/.broker-cred-wiped" 2>/dev/null || true
# 5. Revocation reminder — local deletion ≠ server revocation (decommission §6,
# kill-switch design). The operator MUST also revoke at the source.
_broker_log_err "REMINDER: local deletion does NOT revoke these credentials at the source."
_broker_log_err " Also REVOKE them now so a copy that already egressed cannot be used:"
_broker_log_err " 1. Anthropic Console → Settings → API Keys: DELETE the key for this box."
_broker_log_err " 2. Anthropic Console → account security / Connected apps: REVOKE the"
_broker_log_err " Claude-Code OAuth grant ('sign out everywhere'). See the decommission checklist."
_broker_log_err "── credential cleanup complete ──"
return 0
}
# _broker_log_err — route through larry's err() if present, else stderr. Never
# logs a token or secret.
_broker_log_err() {