<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://wiki.wilsoz.com/index.php?action=history&amp;feed=atom&amp;title=Postmortems%2F2026-05-24_Ultraplan_Exposure</id>
	<title>Postmortems/2026-05-24 Ultraplan Exposure - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://wiki.wilsoz.com/index.php?action=history&amp;feed=atom&amp;title=Postmortems%2F2026-05-24_Ultraplan_Exposure"/>
	<link rel="alternate" type="text/html" href="https://wiki.wilsoz.com/index.php?title=Postmortems/2026-05-24_Ultraplan_Exposure&amp;action=history"/>
	<updated>2026-07-06T07:07:04Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.43.9</generator>
	<entry>
		<id>https://wiki.wilsoz.com/index.php?title=Postmortems/2026-05-24_Ultraplan_Exposure&amp;diff=22&amp;oldid=prev</id>
		<title>Wikibot: Migrated from postmortems/2026-05-24-ultraplan-exposure.md</title>
		<link rel="alternate" type="text/html" href="https://wiki.wilsoz.com/index.php?title=Postmortems/2026-05-24_Ultraplan_Exposure&amp;diff=22&amp;oldid=prev"/>
		<updated>2026-07-05T18:17:37Z</updated>

		<summary type="html">&lt;p&gt;Migrated from postmortems/2026-05-24-ultraplan-exposure.md&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;&amp;#039;&amp;#039;As of: 2026-07-05 — migrated from `postmortems/2026-05-24-ultraplan-exposure.md`&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
= Postmortem &amp;amp; Remediation Playbook: Ultraplan upload exposure (2026-05-24) =&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Status:&amp;#039;&amp;#039;&amp;#039; Remediation in progress. This document is BOTH the postmortem&lt;br /&gt;
and the step-by-step playbook for executing the remediation manually.&lt;br /&gt;
&lt;br /&gt;
== Incident summary ==&lt;br /&gt;
&lt;br /&gt;
While working on the tsbuffer project plan in Claude Code, the &amp;lt;code&amp;gt;/ultraplan&amp;lt;/code&amp;gt;&lt;br /&gt;
command was invoked. &amp;lt;code&amp;gt;/ultraplan&amp;lt;/code&amp;gt; uploads the current working directory&lt;br /&gt;
plus &amp;lt;code&amp;gt;CLAUDE.md&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;MEMORY.md&amp;lt;/code&amp;gt; to an Anthropic-hosted &amp;quot;Claude Code on&lt;br /&gt;
the web&amp;quot; session for remote planning by an agent there.&lt;br /&gt;
&lt;br /&gt;
The remote session terminated with &amp;lt;code&amp;gt;error_during_execution&amp;lt;/code&amp;gt; before&lt;br /&gt;
producing any output. The uploaded content remains in Anthropic cloud&lt;br /&gt;
session storage tied to session ID &amp;lt;code&amp;gt;01GUjtsXpaAw1CqHShfePPmq&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;No warning was surfaced about the upload before it occurred.&amp;#039;&amp;#039;&amp;#039; This is&lt;br /&gt;
the root cause of the exposure — there is no broken security control to&lt;br /&gt;
fix; the tool worked exactly as designed, and we did not know what it did.&lt;br /&gt;
&lt;br /&gt;
== What was in the upload ==&lt;br /&gt;
&lt;br /&gt;
=== Live credentials (rotation required) ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Item&lt;br /&gt;
! Where it was&lt;br /&gt;
! Why it&amp;#039;s bad&lt;br /&gt;
|-&lt;br /&gt;
| n8n API JWT&lt;br /&gt;
| &amp;lt;code&amp;gt;CLAUDE.md&amp;lt;/code&amp;gt;, &amp;quot;n8n API&amp;quot; section, pasted in plaintext, no expiry&lt;br /&gt;
| Anyone with this can call any n8n API endpoint as admin&lt;br /&gt;
|-&lt;br /&gt;
| n8n admin UI password (&amp;lt;code&amp;gt;Htdml0fI1aZgtPBVv5oz&amp;lt;/code&amp;gt;)&lt;br /&gt;
| &amp;lt;code&amp;gt;MEMORY.md&amp;lt;/code&amp;gt;, n8n DB Recovery memory entry&lt;br /&gt;
| Direct UI login as &amp;lt;code&amp;gt;admin@wilsoz.com&amp;lt;/code&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Topology / metadata exposed (no rotation possible, hygiene only) ===&lt;br /&gt;
&lt;br /&gt;
* Internal domains: &amp;lt;code&amp;gt;agent.wilsoz.com&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;vault.wilsoz.com&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;key.wilsoz.com&amp;lt;/code&amp;gt;,&lt;br /&gt;
  &amp;lt;code&amp;gt;photos.wilsoz.com&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ntfy.wilsoz.com&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;mymail.wilsoz.com&amp;lt;/code&amp;gt;,&lt;br /&gt;
  &amp;lt;code&amp;gt;next.wilsoz.com&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;wilsoz.cloudflareaccess.com&amp;lt;/code&amp;gt;.&lt;br /&gt;
* Internal IPs: &amp;lt;code&amp;gt;192.168.0.109&amp;lt;/code&amp;gt; (stronghold), &amp;lt;code&amp;gt;192.168.0.144&amp;lt;/code&amp;gt; (ollama),&lt;br /&gt;
  Docker bridges, Tailscale IP &amp;lt;code&amp;gt;100.67.169.50&amp;lt;/code&amp;gt; for the SDF relay VPS.&lt;br /&gt;
* OpenBao endpoint and secret path layout (paths only, no tokens).&lt;br /&gt;
* Vaultwarden item-naming convention (&amp;quot;Stronghold Infrastructure&amp;quot; folder).&lt;br /&gt;
* MXRoute server &amp;lt;code&amp;gt;blizzard.mxrouting.net&amp;lt;/code&amp;gt;, username &amp;lt;code&amp;gt;craniums&amp;lt;/code&amp;gt;.&lt;br /&gt;
* Authentik admin URL + admin usernames &amp;lt;code&amp;gt;akadmin&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;marcus&amp;lt;/code&amp;gt;; Cloudflare&lt;br /&gt;
  Access OIDC client ID.&lt;br /&gt;
* Matrix bot @zordon:wilsoz.com, room ID, internal Synapse endpoint.&lt;br /&gt;
* All n8n workflow JSONs in &amp;lt;code&amp;gt;n8n-workflows/&amp;lt;/code&amp;gt; and every service doc in&lt;br /&gt;
  &amp;lt;code&amp;gt;services/&amp;lt;/code&amp;gt;.&lt;br /&gt;
* Email addresses including &amp;lt;code&amp;gt;marcus@wilsoz.com&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;admin@wilsoz.com&amp;lt;/code&amp;gt;,&lt;br /&gt;
  &amp;lt;code&amp;gt;jkatz@craniumslows.com&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Top-level constraints for this remediation ==&lt;br /&gt;
&lt;br /&gt;
# &amp;#039;&amp;#039;&amp;#039;The credentials store IS the root of trust.&amp;#039;&amp;#039;&amp;#039; Vaultwarden + OpenBao&lt;br /&gt;
   are how we manage every other secret. Compromising either means&lt;br /&gt;
   compromising everything else.&lt;br /&gt;
# &amp;#039;&amp;#039;&amp;#039;Never paste a secret into chat, ever.&amp;#039;&amp;#039;&amp;#039; Not even temporarily, not&lt;br /&gt;
   even &amp;quot;just to verify.&amp;quot; Once it&amp;#039;s in the conversation, it&amp;#039;s leaked&lt;br /&gt;
   exactly the same way the original JWT was.&lt;br /&gt;
# &amp;#039;&amp;#039;&amp;#039;Verify each new credential works BEFORE deleting the old one.&amp;#039;&amp;#039;&amp;#039; A&lt;br /&gt;
   half-rotated credential where the new one doesn&amp;#039;t work is worse than&lt;br /&gt;
   the original exposure.&lt;br /&gt;
# &amp;#039;&amp;#039;&amp;#039;No secret values in command-line arguments.&amp;#039;&amp;#039;&amp;#039; They show up in&lt;br /&gt;
   &amp;lt;code&amp;gt;/proc/PID/cmdline&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;ps auxw&amp;lt;/code&amp;gt; output. Use stdin, env vars, or&lt;br /&gt;
   token files mode 0600.&lt;br /&gt;
# &amp;#039;&amp;#039;&amp;#039;Two-person rule (where applicable).&amp;#039;&amp;#039;&amp;#039; For high-blast-radius&lt;br /&gt;
   rotations (root tokens, master password) — don&amp;#039;t rotate without&lt;br /&gt;
   confirming the recovery path first.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Remediation steps (manual playbook) ==&lt;br /&gt;
&lt;br /&gt;
Each step has: (a) what to do, (b) how to verify, (c) what to update,&lt;br /&gt;
(d) rollback.&lt;br /&gt;
&lt;br /&gt;
=== Step 1. Rotate n8n API JWT ===&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;(a) Action:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
* Open https://agent.wilsoz.com in a browser; log in as &amp;lt;code&amp;gt;admin@wilsoz.com&amp;lt;/code&amp;gt;.&lt;br /&gt;
  Password: retrieve from Vaultwarden item &amp;quot;n8n UI Login&amp;quot;. (This is the&lt;br /&gt;
  old password — Step 2 will rotate it. The order matters: API key first,&lt;br /&gt;
  because once we change the admin password we want to verify with the&lt;br /&gt;
  API that everything&amp;#039;s healthy.)&lt;br /&gt;
* Settings → n8n API → see the existing key (do NOT copy its value).&lt;br /&gt;
* Click &amp;quot;Revoke&amp;quot; on the existing key. Confirm.&lt;br /&gt;
* Click &amp;quot;Create an API key&amp;quot;. Set &amp;quot;Time to live&amp;quot; to &amp;quot;No expiration&amp;quot;.&lt;br /&gt;
  Copy the new JWT once shown (the UI shows it exactly once).&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Paste the new JWT directly into a Vaultwarden item&amp;#039;&amp;#039;&amp;#039; — open&lt;br /&gt;
  Vaultwarden in another tab, edit item &amp;quot;n8n API Key&amp;quot;, paste, save. Do&lt;br /&gt;
  not paste it into a terminal, a text file, or any chat.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;(b) Verify:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# Retrieve the new key from Vaultwarden via the bw CLI (NOT by pasting):&lt;br /&gt;
NEW_KEY=$(bw get item &amp;quot;n8n API Key&amp;quot; | jq -r &amp;#039;.fields[] | select(.name==&amp;quot;apiKey&amp;quot;) | .value&amp;#039;)&lt;br /&gt;
# (Field name may differ — adjust per your existing Vaultwarden item layout.)&lt;br /&gt;
curl -sf &amp;quot;https://agent.wilsoz.com/api/v1/workflows&amp;quot; \&lt;br /&gt;
  -H &amp;quot;X-N8N-API-KEY: $NEW_KEY&amp;quot; \&lt;br /&gt;
  | jq &amp;#039;.data | length&amp;#039;&lt;br /&gt;
# Expect: a number (count of workflows), no errors.&lt;br /&gt;
unset NEW_KEY&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;(c) Update:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
* Vaultwarden item &amp;quot;n8n API Key&amp;quot;: done above.&lt;br /&gt;
* OpenBao at &amp;lt;code&amp;gt;secret/n8n&amp;lt;/code&amp;gt; field &amp;lt;code&amp;gt;api-key&amp;lt;/code&amp;gt;:&lt;br /&gt;
  ```bash&lt;br /&gt;
  # Read the new key from Vaultwarden into a shell var (no echo):&lt;br /&gt;
  NEW_KEY=$(bw get item &amp;quot;n8n API Key&amp;quot; | jq -r &amp;#039;.fields[] | select(.name==&amp;quot;apiKey&amp;quot;) | .value&amp;#039;)&lt;br /&gt;
  # Write to OpenBao using stdin (NOT command-line arg):&lt;br /&gt;
  printf &amp;#039;%s&amp;#039; &amp;quot;$NEW_KEY&amp;quot; | BAO_TOKEN=&amp;quot;$(cat /library/openbao-app/.claude-mcp-token)&amp;quot; \&lt;br /&gt;
    bao kv put -mount=secret n8n api-key=-&lt;br /&gt;
  unset NEW_KEY&lt;br /&gt;
  ```&lt;br /&gt;
* &amp;lt;code&amp;gt;~/Developer/SystemResiliency/CLAUDE.md&amp;lt;/code&amp;gt;: replace the JWT-bearing line&lt;br /&gt;
  with: &amp;lt;code&amp;gt;Retrieve from Vaultwarden item: &amp;quot;n8n API Key&amp;quot;&amp;lt;/code&amp;gt;. Remove the&lt;br /&gt;
  &amp;lt;code&amp;gt;N8N_KEY=&amp;#039;...&amp;#039;&amp;lt;/code&amp;gt; example line that contains the actual JWT in the&lt;br /&gt;
  curl examples; replace with a &amp;lt;code&amp;gt;# N8N_KEY=$(bw get item &amp;quot;n8n API Key&amp;quot; | ...)&amp;lt;/code&amp;gt;&lt;br /&gt;
  pattern.&lt;br /&gt;
* Commit CLAUDE.md change. Verify the diff contains NO JWT value before&lt;br /&gt;
  committing (&amp;lt;code&amp;gt;git diff CLAUDE.md&amp;lt;/code&amp;gt; and visually scan).&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;(d) Rollback:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
If verification fails, the old JWT was already revoked at n8n — there is&lt;br /&gt;
no rollback to the previous value. Instead, immediately mint another new&lt;br /&gt;
JWT from the n8n UI and retry. The window of &amp;quot;no working API key&amp;quot; is&lt;br /&gt;
acceptable because no automation actively uses it during this rotation.&lt;br /&gt;
&lt;br /&gt;
=== Step 2. Rotate n8n admin UI password ===&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;(a) Action:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
* Still logged into n8n as &amp;lt;code&amp;gt;admin@wilsoz.com&amp;lt;/code&amp;gt;.&lt;br /&gt;
* Settings → Personal → Change Password.&lt;br /&gt;
* Open Vaultwarden, edit item &amp;quot;n8n UI Login&amp;quot;, click the password&lt;br /&gt;
  regenerate button (32 chars, full charset). Save.&lt;br /&gt;
* Copy the new password from Vaultwarden.&lt;br /&gt;
* Paste into n8n&amp;#039;s &amp;quot;New Password&amp;quot; field. Save.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;(b) Verify:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
* Sign out of n8n.&lt;br /&gt;
* Sign in with email &amp;lt;code&amp;gt;admin@wilsoz.com&amp;lt;/code&amp;gt; and the new password.&lt;br /&gt;
* Expect: dashboard loads, workflows visible.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;(c) Update:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
* Vaultwarden item &amp;quot;n8n UI Login&amp;quot;: done above.&lt;br /&gt;
* OpenBao at &amp;lt;code&amp;gt;secret/n8n&amp;lt;/code&amp;gt; field &amp;lt;code&amp;gt;admin-password&amp;lt;/code&amp;gt; (if used by anything):&lt;br /&gt;
  check first whether anything reads it. If yes, update via stdin pattern&lt;br /&gt;
  above. If no, skip.&lt;br /&gt;
* &amp;lt;code&amp;gt;MEMORY.md&amp;lt;/code&amp;gt; (auto-memory): edit &amp;lt;code&amp;gt;/home/mnw/.claude/projects/-home-mnw-Developer-SystemResiliency/memory/MEMORY.md&amp;lt;/code&amp;gt;.&lt;br /&gt;
  Find the line: &amp;lt;code&amp;gt;Created new owner: admin@wilsoz.com / Htdml0fI1aZgtPBVv5oz&amp;lt;/code&amp;gt;&lt;br /&gt;
  Replace with: &amp;lt;code&amp;gt;Created new owner: admin@wilsoz.com / (rotated 2026-05-24; see Vaultwarden &amp;quot;n8n UI Login&amp;quot;)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;(d) Rollback:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
Same situation as Step 1 — old password is already invalidated. If the&lt;br /&gt;
new password doesn&amp;#039;t work, n8n&amp;#039;s password reset relies on SMTP email&lt;br /&gt;
which is functional. Worst case: re-init the n8n DB (same recovery we&lt;br /&gt;
did 2026-03-11).&lt;br /&gt;
&lt;br /&gt;
=== Step 3. Audit CLAUDE.md and MEMORY.md for OTHER secrets ===&lt;br /&gt;
&lt;br /&gt;
This is a one-pass sweep to catch anything I missed when triaging.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;(a) Action:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
cd ~/Developer/SystemResiliency&lt;br /&gt;
# Install gitleaks if not present:&lt;br /&gt;
which gitleaks || sudo apt install -y gitleaks&lt;br /&gt;
# Scan the project + memory dirs:&lt;br /&gt;
gitleaks detect --source . --no-git -v&lt;br /&gt;
gitleaks detect --source ~/.claude/projects/-home-mnw-Developer-SystemResiliency/memory --no-git -v&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;(b) Verify:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
* Read the findings line by line.&lt;br /&gt;
* For each finding: is it actually a secret, or a false positive (e.g.&lt;br /&gt;
  an ID, a hash of a non-sensitive value)?&lt;br /&gt;
* True positives: add to the rotation list, restart from Step 1 for each.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;(c) Update:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
* Document any new findings in this file (append a &amp;quot;Found in audit&amp;quot;&lt;br /&gt;
  subsection below).&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;(d) Rollback:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
N/A — read-only operation.&lt;br /&gt;
&lt;br /&gt;
=== Step 4. Attempt deletion of the cloud session ===&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;(a) Action:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
* Open https://claude.ai/code/session_01GUjtsXpaAw1CqHShfePPmq in a&lt;br /&gt;
  browser. Log in as &amp;lt;code&amp;gt;marcus@wilsoz.com&amp;lt;/code&amp;gt;.&lt;br /&gt;
* Look for a delete / remove option in the session menu (three-dot icon,&lt;br /&gt;
  settings gear, or similar).&lt;br /&gt;
* If found, click it. Confirm.&lt;br /&gt;
* If not found: send an email to &amp;lt;code&amp;gt;privacy@anthropic.com&amp;lt;/code&amp;gt; with the session&lt;br /&gt;
  ID and a request for deletion under data-deletion rights. Reference&lt;br /&gt;
  https://privacy.anthropic.com. Subject:&lt;br /&gt;
  &amp;lt;code&amp;gt;Data deletion request — session 01GUjtsXpaAw1CqHShfePPmq&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;(b) Verify:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
* If UI delete worked: refresh the session URL, expect 404 or&lt;br /&gt;
  &amp;quot;session not found&amp;quot;.&lt;br /&gt;
* If email request: save the sent message; expect a reply within 30 days&lt;br /&gt;
  per their stated SLA.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;(c) Update:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
* Append the outcome to this file: &amp;quot;Cloud session deletion: [UI delete |&lt;br /&gt;
  email requested on YYYY-MM-DD | response received on YYYY-MM-DD]&amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;(d) Rollback:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
N/A. Best-effort operation either way.&lt;br /&gt;
&lt;br /&gt;
=== Step 5. Install a pre-commit secret-scanner ===&lt;br /&gt;
&lt;br /&gt;
So a future paste-in is caught before it reaches a commit.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;(a) Action:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
cd ~/Developer/SystemResiliency&lt;br /&gt;
# Add a .gitleaks.toml if you want custom rules; defaults are fine.&lt;br /&gt;
# Install the pre-commit framework if not already:&lt;br /&gt;
which pre-commit || sudo apt install -y pre-commit&lt;br /&gt;
cat &amp;gt; .pre-commit-config.yaml &amp;lt;&amp;lt;&amp;#039;EOF&amp;#039;&lt;br /&gt;
repos:&lt;br /&gt;
  - repo: https://github.com/gitleaks/gitleaks&lt;br /&gt;
    rev: v8.18.0&lt;br /&gt;
    hooks:&lt;br /&gt;
      - id: gitleaks&lt;br /&gt;
EOF&lt;br /&gt;
pre-commit install&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Do the same in &amp;lt;code&amp;gt;~/Developer/jellyfin-adhoc/&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;~/Developer/tsbuffer/&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;(b) Verify:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
* Make a trivial change to a file in the repo.&lt;br /&gt;
* Stage it. Try to commit.&lt;br /&gt;
* Expect: pre-commit runs, gitleaks scans the staged diff, commit&lt;br /&gt;
  proceeds (because the diff is clean).&lt;br /&gt;
* Manually test the catch: stage a file containing &amp;lt;code&amp;gt;EXAMPLE_KEY=eyJabc.def.ghi&amp;lt;/code&amp;gt;,&lt;br /&gt;
  commit; expect gitleaks to block.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;(c) Update:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
* Commit the &amp;lt;code&amp;gt;.pre-commit-config.yaml&amp;lt;/code&amp;gt; to each repo.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;(d) Rollback:&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&amp;lt;code&amp;gt;pre-commit uninstall&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Step 6. Adopt a policy on cloud-handoff tools ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;/ultraplan&amp;lt;/code&amp;gt; is convenient but its blast radius is the entire working&lt;br /&gt;
directory. Several reasonable options:&lt;br /&gt;
&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Block it entirely&amp;#039;&amp;#039;&amp;#039; via a Claude Code hook in &amp;lt;code&amp;gt;~/.claude/settings.json&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Allow but require confirmation&amp;#039;&amp;#039;&amp;#039; via a pre-tool hook that shows the&lt;br /&gt;
  user the upload manifest before proceeding.&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Sanitize first&amp;#039;&amp;#039;&amp;#039; — never invoke from a directory that contains&lt;br /&gt;
  CLAUDE.md / MEMORY.md.&lt;br /&gt;
&lt;br /&gt;
Decision pending — capture in &amp;lt;code&amp;gt;~/Developer/SystemResiliency/TODO.md&amp;lt;/code&amp;gt; for&lt;br /&gt;
follow-up after this incident closes.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Outcome (filled in as work happens) ==&lt;br /&gt;
&lt;br /&gt;
* [ ] Step 1 — n8n API JWT rotated&lt;br /&gt;
* [ ] Step 2 — n8n admin password rotated&lt;br /&gt;
* [ ] Step 3 — Audit complete; findings:&lt;br /&gt;
* [ ] Step 4 — Cloud session deletion attempt:&lt;br /&gt;
* [ ] Step 5 — Pre-commit hook installed&lt;br /&gt;
* [ ] Step 6 — Cloud-handoff policy decision:&lt;br /&gt;
&lt;br /&gt;
== Follow-ups (for TODO.md) ==&lt;br /&gt;
&lt;br /&gt;
* Cloud-handoff tool policy decision (Step 6 above).&lt;br /&gt;
* After this exposure, audit existing OpenBao &amp;lt;code&amp;gt;scripts-n8n&amp;lt;/code&amp;gt; and&lt;br /&gt;
  &amp;lt;code&amp;gt;claude-mcp&amp;lt;/code&amp;gt; tokens: are they scoped tightly enough? Should they be&lt;br /&gt;
  rotated on a schedule (e.g. every 90 days) regardless of leaks?&lt;br /&gt;
* Consider whether Vaultwarden master password should be rotated on a&lt;br /&gt;
  cadence (high blast radius if leaked; can&amp;#039;t be auto-rotated).&lt;br /&gt;
&lt;br /&gt;
== Lessons ==&lt;br /&gt;
&lt;br /&gt;
# Any Claude Code tool that &amp;quot;uploads context&amp;quot; needs an explicit&lt;br /&gt;
   user-confirmation prompt with the manifest of what will be sent. The&lt;br /&gt;
   silent default is a footgun.&lt;br /&gt;
# CLAUDE.md and MEMORY.md are read by everything: every Claude Code&lt;br /&gt;
   session, plus any tool that exports project context. They must never&lt;br /&gt;
   contain literal secrets. The bar should be &amp;quot;would I publish this to&lt;br /&gt;
   a public README?&amp;quot; — if no, it doesn&amp;#039;t belong in those files.&lt;br /&gt;
# Rotation tedium is the actual problem behind &amp;quot;I&amp;#039;ll just inline this&lt;br /&gt;
   credential for now.&amp;quot; The exposure-remediation script (see&lt;br /&gt;
   &amp;lt;code&amp;gt;scripts/exposure-remediation/PLAN.md&amp;lt;/code&amp;gt;) is the load-bearing follow-up.&lt;/div&gt;</summary>
		<author><name>Wikibot</name></author>
	</entry>
</feed>