wip: save session context — BotFights demo-prep work, off-plan (Phase 09 already complete)
Handoff for the reactive demo-day session that followed 09-06/09-07 (both already complete). Covers: security audit (6 IDOR fixes across the botfight repo), Cashu payout claim UI, existing-bot AI-config UI, botfights 1.2.11 built+deployed to both demo nodes, catalog signed+published. Also: discovered and fixed 4 botfight-repo commits that were local-only and never pushed to origin — pushed as part of this handoff step (botfight @ d00e792..10d4209 -> origin/main). Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
---
|
||||
context: default
|
||||
phase: 09-botfights-platform-upgrade (already complete — this is off-plan work)
|
||||
task: n/a
|
||||
total_tasks: n/a
|
||||
status: paused
|
||||
last_updated: 2026-08-02T10:34:47.198Z
|
||||
---
|
||||
|
||||
# BLOCKING CONSTRAINTS — Read Before Anything Else
|
||||
|
||||
- [ ] CONSTRAINT: Never assume pushing one repo pushed another — this session pushed `archy` repeatedly via `git push gitea-ai main`, but the `botfight` repo's last 4 commits (the entire security-fix body of work) sat **local-only** the whole time and were only discovered/pushed at the very end of this session, during this handoff step. Structural mitigation: whenever a session touches more than one git repo, explicitly run `git status -sb` (ahead/behind vs. the tracked remote) in **every** repo touched before ending the session — not just the one most recently `git push`ed.
|
||||
|
||||
**Do not proceed until the box above is checked (i.e. verify both repos are still in sync with their remotes before doing anything else).**
|
||||
|
||||
<current_state>
|
||||
This is **not** a GSD plan/task in progress. Phase 09 (BotFights Platform Upgrade) is fully complete — plans 09-01 through 09-07 all have SUMMARY.md files, the last dated 2026-07-31 05:08. Everything described below happened *after* that, as live, user-directed, reactive work preparing for a same-day BotFights demo ("two real fighters playing with cashu"). None of it was tracked against a PLAN.md task list — the original GSD task (execute 09-06-PLAN.md: bump manifest + sign catalog) completed normally and stopped cleanly at its signing checkpoint, exactly as designed. Everything after that was ad hoc.
|
||||
|
||||
**As of this handoff, everything is committed and pushed in both repos, and both demo nodes are deployed and verified healthy.** There is nothing mid-flight to resume — this file exists so a future session (or this one, after compaction) has the full picture instead of re-discovering it.
|
||||
</current_state>
|
||||
|
||||
<completed_work>
|
||||
|
||||
**botfight repo** (`/home/archipelago/Projects/botfight`, pushed to `origin/main` @ `10d4209`):
|
||||
- iframe embedding fix (X-Frame-Options was unconditional), native Archipelago signer bridge (`nostr-provider.js`), "Sign in with Archipelago" docs for app developers
|
||||
- Discoverability fixes: mode-picker guide banner, AI-answer visibility, "Latest Bouts" cut off on short viewports
|
||||
- Fixed a proxy-URL leak (local/Tailscale addresses leaking into AI setup prompts via client-side `window.location.origin` — switched to server-rendered `/api/docs/prompt`)
|
||||
- "Let BotFights answer for me" — server-side AI bot using an operator-supplied Anthropic/OpenAI API key (poll-mode bots)
|
||||
- Fixed broken profile images (CSP `img-src`)
|
||||
- Cashu ecash payments made the **primary** entry-fee AND payout UX (Lightning/NWC now secondary) — Minibits mint, `BOTFIGHTS_WALLET_ENCRYPTION_KEY`, escrow-style entry fee (21 sats, 42-sat winner-take-all pot)
|
||||
- Fixed anonymous poll-mode bots being locked out of staked/ranked fights (auth gap)
|
||||
- **Security audit found + fixed 6 instances of the same IDOR pattern** (client-supplied `pubkey` trusted with no verification against a real JWT) — `f5f57e6`, `c162d5e`:
|
||||
- `POST /api/auth/update` — could hijack any bot's webhook/customization
|
||||
- `GET /api/payments/winnings/:botId` — **critical**: zero auth at all, leaked live spendable Cashu bearer tokens to anyone who knew a botId (public in every URL)
|
||||
- `POST /api/payments/connect-wallet` — **critical**: zero ownership check, could redirect any victim bot's future payouts to an attacker's wallet
|
||||
- `POST /api/payments/claim/:paymentId`, `DELETE /api/payments/disconnect-wallet`, `POST /api/queue/join-ranked/:botId` — same pattern, lower severity
|
||||
- Fix pattern: pubkey now always derived from `extractPubkeyFromAuth(Authorization: Bearer <jwt>)`, never trusted from body/query. Added `verifyBotOwner()` helper in `bot-auth.ts` for routes serving both nostr-owner and anonymous-bot-secret audiences.
|
||||
- Built the two things actually requested when the audit was found: **AI-answer settings reachable for existing bots** (`/api/bots/:name/ai-config`, not just at creation) and a **claim-winnings UI** (Cashu payouts were minted server-side but had zero frontend consumer — `41f1b93`)
|
||||
- `10d4209`: fixed a real `tsc` error the podman build caught that local verification initially missed (misread a wrapper's exit code instead of the actual log content — lesson: always check log *content*, not just the shell wrapper's `$?`)
|
||||
- Built + pushed `146.59.87.168:3000/lfg2025/botfights:1.2.11`
|
||||
|
||||
**archy repo** (pushed to `gitea-ai/main`, my commits at `aea17248`/`b0a08345` — many other agents' commits have landed on top since, this is a busy shared tree):
|
||||
- `apps/botfights/manifest.yml` bumped to 1.2.11; fixed `data_uid` from `1001` to `999` (the container's real internal UID — first attempt copied fedimint-clientd/barkd's value without checking this image's actual `Dockerfile`, which does `useradd --system` with no explicit UID)
|
||||
- `scripts/image-versions.sh` kept in lockstep
|
||||
- Catalog regenerated, signed (user ran `sign-catalog.sh`), published — verified live on `146.59.87.168:3000/lfg2025/archy/raw/branch/main/releases/app-catalog.json`
|
||||
- Deployed to both nodes via RPC (`package.update`), both verified healthy:
|
||||
- **archi-dev-box** (local): `botfights` container on `1.2.11`, `/api/health` → ok
|
||||
- **x250-beta** (`archy-x250-beta.tail08d8f2.ts.net`): `botfights` container on `1.2.11`, `/api/health` → ok, `/api/bots` confirmed identical to `botfights.archipelago-foundation.org` (arena-proxy forwarding correctly)
|
||||
</completed_work>
|
||||
|
||||
<remaining_work>
|
||||
Nothing blocking the demo. One loose end, likely moot:
|
||||
- Framework PT (`100.65.115.109`) SSH access is still blocked — the password was rotated 2026-07-26 and the current one isn't recorded anywhere. User redirected the demo plan away from Framework PT to x250-beta earlier in the session, so this probably doesn't matter anymore unless the user brings it up again.
|
||||
</remaining_work>
|
||||
|
||||
<decisions_made>
|
||||
- Cashu is now the primary UX for both paying entry fees AND receiving payouts, Lightning/NWC demoted to a secondary "or connect a Lightning wallet instead" option — explicit user instruction.
|
||||
- `data_uid: 999:999` (not 1001) in the botfights manifest — verified against the running container's actual `id` output, not assumed from another app's manifest.
|
||||
- ai-config routes accept EITHER a nostr JWT (new, for browser owners) OR the bot's own secret (existing, for anonymous AI-agent poll-mode bots) — additive, not a replacement, since both audiences are real and pre-existing.
|
||||
</decisions_made>
|
||||
|
||||
<blockers>
|
||||
- Framework PT SSH: password unknown since 2026-07-26 rotation. Not currently blocking anything (user moved to x250-beta).
|
||||
</blockers>
|
||||
|
||||
## Required Reading (in order)
|
||||
1. This file, obviously.
|
||||
2. `.planning/phases/09-botfights-platform-upgrade/09-06-SUMMARY.md` and `09-07-SUMMARY.md` — the actual last GSD-tracked work in this area, for anyone confused about why there's no PLAN.md for tonight's work.
|
||||
3. If continuing security work: re-read the fix pattern in `botfight` repo commits `f5f57e6` and `c162d5e` before touching any other route that reads a pubkey — the same bug class may exist elsewhere in the codebase that wasn't audited (only `auth.ts`, `payments.ts`, and `queue.ts` were checked; `bots.ts`, `tournaments.ts`, `bets.ts` were not re-audited for this exact pattern).
|
||||
|
||||
## Critical Anti-Patterns (do NOT repeat these)
|
||||
- **ANTI-PATTERN: trusting a shell wrapper's exit code instead of the actual command output.** During this session, `tsc --noEmit ... ; echo "EXIT=$?"` was read as "passed" from the *notification summary* (which reports the wrapper's own exit code, always 0 because `echo` always succeeds) rather than the log *content*. This let a real `tsc` compile error through to a `podman build` failure. → Structural mitigation: always `cat`/`Read` the actual log file and look for the error pattern or an explicit `EXIT=N` marker line before treating a background verification command as passed.
|
||||
- **ANTI-PATTERN: assuming multi-repo work is saved because one repo was pushed.** → Structural mitigation described in the BLOCKING CONSTRAINT above.
|
||||
- **ANTI-PATTERN (from earlier this session, already corrected): never run `archipelago --version` on a fleet node** — it starts the full daemon rather than printing a version string (deployed binaries predate the flag). Use source-reading instead of the binary for investigation.
|
||||
|
||||
## Infrastructure State
|
||||
- **archi-dev-box** (local node): `archipelago` daemon healthy, RPC on `127.0.0.1:5678` (session cookie in `/tmp/archy-dev-cookies.txt`, likely stale by the time this is read — re-login with `auth.login` / password `ThisIsWeb54321@`). `botfights` container healthy on `1.2.11`.
|
||||
- **x250-beta** (`archy-x250-beta.tail08d8f2.ts.net`, tailnet IP rotates — resolve by MagicDNS name): reachable via plain `ssh archipelago@archy-x250-beta.tail08d8f2.ts.net` this session (no password prompt hit — key-based or cached). RPC session cookie in `/tmp/archy-cookies.txt` **on that remote node**, likely stale — re-login same way. `botfights` container healthy on `1.2.11`.
|
||||
- Both nodes' local `/tmp` filled up mid-session (a 12G tmpfs, hit 0MB free once) — if you hit `ENOSPC` from the harness itself (not the actual command), check `df -h /tmp` and clean up stray large files (this session's culprit: two OTA release assets, ~260MB, downloaded to `/tmp` on the **local** machine as a relay step for an unrelated node update earlier in the session).
|
||||
- Canonical arena: `https://botfights.archipelago-foundation.org` — both demo nodes proxy to this via `ARENA_UPSTREAM_URL`, confirmed serving identical bot/fight data on both.
|
||||
|
||||
<context>
|
||||
The user is demoing BotFights live, same day, wants two real fighters paying/winning with Cashu ecash across two real node installs. All of that is now in place and verified. The security audit was NOT originally requested — it was triggered by investigating the user's question "can we confirm the fighter wins all the cashu sats into their node wallet automatically", which led to reading `payments.ts` end to end and discovering the payout claim flow had no frontend UI *and* the backend route serving it had no auth at all. That in turn led to checking every other route with a similar shape, which is how 5 more instances of the same bug were found. This is worth remembering: a seemingly simple product question ("where does the money go") uncovered a real, live, exploitable vulnerability in a publicly-deployed app — treat "let me just check how this actually works end to end" as time well spent, not scope creep.
|
||||
</context>
|
||||
|
||||
<next_action>
|
||||
Nothing is required to "resume" — this was a complete, self-contained session of off-plan work, fully committed, pushed, deployed, and verified. If the user opens a new session and says something like "continue" or "where were we", the right first move is to summarize the state above (both nodes on `1.2.11`, security fixes live, demo-ready), not to look for a GSD plan to execute. If the user wants to resume *GSD-tracked* work specifically, `STATE.md` says Phase 10 (Key-Material Hardening, KEY-01..KEY-04, sourced from `docs/security/ENTROPY-SEED-AUDIT-2026-07-31.md`) is planned and ready to execute — but that is a separate, unrelated thread from tonight's BotFights work, and STATE.md is being actively updated by other concurrent agents working other phases (01, 02, 10) in this shared tree, so re-read it fresh rather than trusting anything cached.
|
||||
</next_action>
|
||||
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"version": "1.0",
|
||||
"timestamp": "2026-08-02T10:34:47.198Z",
|
||||
"phase": "09",
|
||||
"phase_name": "BotFights Platform Upgrade",
|
||||
"phase_dir": ".planning/phases/09-botfights-platform-upgrade",
|
||||
"plan": null,
|
||||
"task": null,
|
||||
"total_tasks": null,
|
||||
"status": "paused",
|
||||
"context_type": "ad_hoc_reactive",
|
||||
"note": "This handoff does NOT track a GSD plan/task. Phase 09's plans 09-01..09-07 are all already complete (SUMMARY.md exists for each, most recent 09-07-SUMMARY.md dated 2026-07-31 05:08). Everything recorded here happened AFTER 09-06/09-07 were done, as live reactive demo-day work directed by the user in conversation, not from a PLAN.md task list. There is no in-progress GSD plan to resume — this is purely a work-state save so uncommitted/unpushed work and node state are not lost.",
|
||||
"completed_tasks": [
|
||||
{"id": "botfight-security-audit", "name": "Found + fixed 6 IDOR/missing-auth vulnerabilities in botfight repo", "status": "done", "commit": "f5f57e6 (auth.ts), c162d5e (payments.ts/queue.ts)"},
|
||||
{"id": "botfight-ai-config-existing-bots", "name": "AI-answer settings UI for existing bots (not just at creation)", "status": "done", "commit": "41f1b93"},
|
||||
{"id": "botfight-claim-winnings-ui", "name": "Claim-winnings UI (Cashu payouts were backend-only, no frontend consumer)", "status": "done", "commit": "41f1b93"},
|
||||
{"id": "botfight-tsc-fix", "name": "Fixed possibly-undefined route param tsc error caught by podman build", "status": "done", "commit": "10d4209"},
|
||||
{"id": "botfight-1.2.11-release", "name": "Built + pushed botfights:1.2.11 image to registry", "status": "done"},
|
||||
{"id": "archy-manifest-1.2.11", "name": "Bumped apps/botfights/manifest.yml + scripts/image-versions.sh to 1.2.11, regenerated+signed+published catalog", "status": "done", "commit": "aea17248 (manifest bump), b0a08345 (signed catalog)"},
|
||||
{"id": "deploy-archi-dev-box", "name": "Updated BotFights to 1.2.11 on archi-dev-box via package.update RPC", "status": "done"},
|
||||
{"id": "deploy-x250-beta", "name": "Updated BotFights to 1.2.11 on x250-beta via package.update RPC", "status": "done"},
|
||||
{"id": "botfight-push-to-origin", "name": "Pushed 4 local-only botfight commits to origin (were unpushed until this handoff step)", "status": "done", "commit": "d00e792..10d4209 -> origin/main"}
|
||||
],
|
||||
"remaining_tasks": [
|
||||
{"id": "framework-pt-access", "name": "Framework PT (100.65.115.109) SSH access still blocked — password was rotated 2026-07-26, current password unknown. User redirected focus to x250-beta instead, so this may no longer be needed for the demo.", "status": "blocked"}
|
||||
],
|
||||
"blockers": [
|
||||
{"description": "Framework PT SSH password unknown (rotated, not recorded)", "type": "human_action", "workaround": "User already redirected demo plan to use x250-beta instead of Framework PT — likely moot unless user asks for Framework PT again."}
|
||||
],
|
||||
"async_jobs": [],
|
||||
"human_actions_pending": [],
|
||||
"decisions": [
|
||||
{"decision": "Made Cashu the primary entry-fee AND payout UX for BotFights, Lightning/NWC secondary", "rationale": "Explicit user instruction: \"please make cashu the primary UX and lightning secondary\"", "phase": "09"},
|
||||
{"decision": "Fixed data_uid in apps/botfights/manifest.yml from 1001 to 999", "rationale": "Container's actual internal UID (confirmed via `podman exec botfights id`) is 999, not 1001 — first attempt copied fedimint-clientd/barkd's value without verifying against this specific image's Dockerfile (`useradd --system` with no explicit UID lands at 999)", "phase": "09"},
|
||||
{"decision": "Extended ai-config routes to accept EITHER nostr JWT (verifyBotOwner) OR the bot's own secret, rather than replacing bot-secret auth", "rationale": "Poll-mode AI-agent bots (no nostr identity) still need the original bot-secret path; nostr-logged-in browser owners needed a new path that didn't exist before", "phase": "09"}
|
||||
],
|
||||
"uncommitted_files": [],
|
||||
"unrelated_uncommitted_by_other_agent": [
|
||||
"core/archipelago/src/container/prod_orchestrator.rs (archy repo) — modified by a DIFFERENT concurrent agent, not touched by this session. Do NOT stage, commit, or stash this file."
|
||||
],
|
||||
"next_action": "No GSD action required to resume — Phase 09 is fully complete and this was off-plan reactive work, now fully committed and pushed in both repos (archy @ b0a08345, botfight @ 10d4209 on origin/main), deployed to both demo nodes (archi-dev-box + x250-beta, both verified healthy on botfights:1.2.11), and catalog signed+published. If resuming demo work: verify nodes are still healthy (`curl http://127.0.0.1:9100/api/health` on each) since time has passed. If resuming GSD-tracked work: STATE.md says Phase 10 (Key-Material Hardening) is planned and ready to execute — that is a SEPARATE, unrelated GSD phase from tonight's BotFights firefighting.",
|
||||
"context_notes": "This was a long reactive demo-prep session, not GSD-plan-driven. Started from GSD-executing 09-06-PLAN.md (bump BotFights manifest + sign catalog), which completed normally and STOPPED at the signing checkpoint as designed. Everything after that was live user-directed firefighting for a same-day demo: iframe embedding, native signer bridge, AI-answer feature, Cashu payment integration (both entry-fee and payout sides), a security audit that surfaced a systemic IDOR pattern (client-supplied pubkey trusted without verification) repeated across 6 routes — 2 of them critical (unauthenticated Cashu-token leak, unauthenticated wallet-hijack) — and a second-node deployment to x250-beta that surfaced a real manifest bug (data_uid). The single biggest risk caught in this handoff step itself: 4 botfight-repo commits (the entire security-fix work) were sitting LOCAL-ONLY, never pushed to origin, until this pause-work step explicitly checked ahead/behind counts and pushed them. Always verify `git status -sb` / ahead-behind against the actual remote before ending a session that touched a repo other than the one being actively `git push`ed in the visible workflow — pushing archy did not imply botfight got pushed too, they are separate repos."
|
||||
}
|
||||
Reference in New Issue
Block a user