← back to Secrets Manager
durable daily post-bubbe Tier-1 secret-rotation reminder
89bebbabc8ba4741d2ccfca2b8353bbe98f6d315 · 2026-05-31 16:43:51 -0700 · Steve Abrams
LaunchAgent-driven (com.steve.postbubbe-rotation-reminder, 8:37am daily,
survives restarts) replacement for the session-only cron. Compares each
Tier-1 key's registry digest against the 2026-05-31 baseline; a changed
digest = rotated. Fires a macOS notification listing keys still un-rotated,
and SELF-RETIRES (boots out the agent + drops a RESOLVED marker) once all 9
have been rotated. Re-verify + remind only — never rotates console-only keys.
Tracks: the reminder script + the digest baseline (digests are last4:sha-prefix,
not secrets). Plist lives in ~/Library/LaunchAgents (outside the repo).
Files touched
A postbubbe-rotation-reminder.shA postbubbe-tier1-baseline.json
Diff
commit 89bebbabc8ba4741d2ccfca2b8353bbe98f6d315
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Sun May 31 16:43:51 2026 -0700
durable daily post-bubbe Tier-1 secret-rotation reminder
LaunchAgent-driven (com.steve.postbubbe-rotation-reminder, 8:37am daily,
survives restarts) replacement for the session-only cron. Compares each
Tier-1 key's registry digest against the 2026-05-31 baseline; a changed
digest = rotated. Fires a macOS notification listing keys still un-rotated,
and SELF-RETIRES (boots out the agent + drops a RESOLVED marker) once all 9
have been rotated. Re-verify + remind only — never rotates console-only keys.
Tracks: the reminder script + the digest baseline (digests are last4:sha-prefix,
not secrets). Plist lives in ~/Library/LaunchAgents (outside the repo).
---
postbubbe-rotation-reminder.sh | 62 ++++++++++++++++++++++++++++++++++++++++++
postbubbe-tier1-baseline.json | 42 ++++++++++++++++++++++++++++
2 files changed, 104 insertions(+)
diff --git a/postbubbe-rotation-reminder.sh b/postbubbe-rotation-reminder.sh
new file mode 100755
index 0000000..45d8370
--- /dev/null
+++ b/postbubbe-rotation-reminder.sh
@@ -0,0 +1,62 @@
+#!/usr/bin/env bash
+# postbubbe-rotation-reminder.sh
+# Durable daily reminder for the OUTSTANDING post-bubbe-RCE Tier-1 secret rotation.
+# Compares each Tier-1 key's current registry digest against the 2026-05-31
+# baseline; a key is ROTATED when its digest changes (Steve regenerates in the
+# provider console, then `cli.js import-paste` rewrites the digest).
+# - any still un-rotated -> macOS notification + log line, exit 0
+# - ALL rotated -> success notification, SELF-RETIRE (unload + disable
+# the LaunchAgent), drop a RESOLVED marker, exit 0
+# Self-contained, no args. Driven by com.steve.postbubbe-rotation-reminder.
+set -uo pipefail
+
+SM="$HOME/Projects/secrets-manager"
+BASELINE="$SM/postbubbe-tier1-baseline.json"
+REGISTRY="$SM/registry.json"
+LOG="$SM/postbubbe-rotation-reminder.log"
+PLIST="$HOME/Library/LaunchAgents/com.steve.postbubbe-rotation-reminder.plist"
+LABEL="com.steve.postbubbe-rotation-reminder"
+MARKER="$SM/.postbubbe-rotation-RESOLVED"
+STAMP="$(date '+%Y-%m-%d %H:%M:%S')"
+
+note(){ printf '[%s] %s\n' "$STAMP" "$*" >> "$LOG"; }
+notify(){ # title, message
+ /usr/bin/osascript -e "display notification \"$2\" with title \"$1\" sound name \"Glass\"" >/dev/null 2>&1 || true
+}
+
+[ -f "$BASELINE" ] || { note "ERROR baseline missing: $BASELINE"; exit 0; }
+[ -f "$REGISTRY" ] || { note "ERROR registry missing: $REGISTRY"; exit 0; }
+
+# Compare digests in node (jq may be absent); print "ROTATED a b" / "PENDING x y".
+RESULT="$(BASELINE="$BASELINE" REGISTRY="$REGISTRY" /usr/bin/env node -e '
+const fs=require("fs");
+const base=JSON.parse(fs.readFileSync(process.env.BASELINE,"utf8")).keys||{};
+const reg=JSON.parse(fs.readFileSync(process.env.REGISTRY,"utf8")).secrets||{};
+const pending=[], rotated=[];
+for(const k of Object.keys(base)){
+ const was=base[k].digest||null;
+ const now=(reg[k]&®[k].digest)||null;
+ if(now && was && now!==was) rotated.push(k); else pending.push(k);
+}
+console.log("PENDING\t"+pending.join(","));
+console.log("ROTATED\t"+rotated.join(","));
+' 2>>"$LOG")"
+
+PENDING="$(printf '%s\n' "$RESULT" | awk -F'\t' '/^PENDING/{print $2}')"
+ROTATED="$(printf '%s\n' "$RESULT" | awk -F'\t' '/^ROTATED/{print $2}')"
+PCOUNT=0; [ -n "$PENDING" ] && PCOUNT="$(printf '%s' "$PENDING" | tr ',' '\n' | grep -c .)"
+
+if [ "$PCOUNT" -eq 0 ]; then
+ note "ALL Tier-1 keys rotated ($ROTATED). Retiring reminder."
+ notify "✅ Post-bubbe rotation complete" "All Tier-1 keys rotated. Retiring the daily reminder."
+ printf 'RESOLVED %s\nrotated: %s\n' "$STAMP" "$ROTATED" > "$MARKER"
+ # Self-retire the LaunchAgent (modern bootout, fall back to legacy unload).
+ launchctl bootout "gui/$(id -u)/$LABEL" 2>/dev/null || launchctl unload "$PLIST" 2>/dev/null || true
+ # Disable RunAtLoad/relaunch by renaming the plist aside so it won't reload.
+ [ -f "$PLIST" ] && mv "$PLIST" "$PLIST.retired-$(date +%Y%m%d)" 2>/dev/null || true
+ exit 0
+fi
+
+note "STILL OUTSTANDING ($PCOUNT): $PENDING"
+notify "🔒 Rotate $PCOUNT post-bubbe keys" "Still live & unrotated: ${PENDING}. Regenerate in console → cli.js import-paste. Order: CF/GoDaddy/Stripe/Shopify → OpenAI/Gemini/ElevenLabs."
+exit 0
diff --git a/postbubbe-tier1-baseline.json b/postbubbe-tier1-baseline.json
new file mode 100644
index 0000000..2d04aaa
--- /dev/null
+++ b/postbubbe-tier1-baseline.json
@@ -0,0 +1,42 @@
+{
+ "captured_at": "2026-05-31",
+ "note": "post-bubbe RCE Tier-1 baseline digests; key is ROTATED when its registry digest differs from this value",
+ "keys": {
+ "STRIPE_SECRET_KEY": {
+ "digest": "8LQ0:fe981600",
+ "last_updated": "2026-05-01T06:23:30.081Z"
+ },
+ "CLOUDFLARE_API_TOKEN": {
+ "digest": "e700:3609b6eb",
+ "last_updated": "2026-05-18T19:25:46.508Z"
+ },
+ "GODADDY_API_KEY": {
+ "digest": "acsF:96974d0c",
+ "last_updated": "2026-04-30T18:50:35.250Z"
+ },
+ "GODADDY_API_SECRET": {
+ "digest": "Vnej:31492271",
+ "last_updated": "2026-04-30T18:50:40.119Z"
+ },
+ "SHOPIFY_ADMIN_TOKEN": {
+ "digest": "75b9:95b01e9b",
+ "last_updated": "2026-05-07T23:26:22.519Z"
+ },
+ "SHOPIFY_DW_ADMIN_TOKEN_CONTENT": {
+ "digest": "19b4:8d306dfc",
+ "last_updated": "2026-05-08T06:19:32.976Z"
+ },
+ "OPENAI_API_KEY": {
+ "digest": "FYwA:8ad69c60",
+ "last_updated": "2026-05-01T05:54:19.819Z"
+ },
+ "GEMINI_API_KEY": {
+ "digest": "k720:4295091b",
+ "last_updated": "2026-05-02T15:34:58.966Z"
+ },
+ "ELEVENLABS_API_KEY": {
+ "digest": "365a:7f1a7de6",
+ "last_updated": "2026-05-06T21:37:52.553Z"
+ }
+ }
+}
\ No newline at end of file
← a1e5d79 gitignore: add patterns for backup/scratch files (*.bak, *.b
·
back to Secrets Manager
·
Add credential rotation checklist (dw_admin pw + 2 Gemini ke 4fbb01c →