fix: broken profile images (CSP img-src), AI-answer discoverability, arena compose tag
CI / check (push) Failing after 6m10s

1. server/src/app.ts: CSP img-src only allowed 'self'/data:/blob: — nostr
   profile pictures come from kind:0 metadata, a URL the user sets via their
   own client, hosted on whatever domain they picked. There's no central
   image host for a decentralized identity system, so every external
   profile pic was CSP-blocked and rendered as a broken image. Added
   https: (broad) — safe here since images can't execute script even from
   an untrusted origin, unlike script-src which stays locked to 'self'.

2. frontend/src/pages/JoinBoutPage.vue: the new AI-answer option (1.2.7)
   was reported as invisible — it was gated behind picking POLLING (not the
   default WEBHOOK) AND behind a collapsed toggle within that. Changed:
   POLLING is now the default mode (also the documented default in
   BOTFIGHTS.md), the AI section is expanded by default instead of
   collapsed, and the POLLING button's own description now mentions the
   option so it's visible without any extra click.

3. docker-compose.arena.yml: image tag 1.2.1 -> 1.2.7, matching what's
   actually deployed on the canonical arena (rolled live via
   ssh+docker compose pull/up this session — this commit just brings the
   repo's copy of the compose file back in sync with reality).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-07-31 08:52:21 -04:00
co-authored by Claude Fable 5
parent ca5b63468e
commit 877d1f6389
3 changed files with 21 additions and 5 deletions
+1 -1
View File
@@ -18,7 +18,7 @@
services:
botfights-arena:
image: localhost:3000/lfg2025/botfights:1.2.1
image: localhost:3000/lfg2025/botfights:1.2.7
container_name: botfights-arena
restart: unless-stopped
ports:
+12 -3
View File
@@ -33,7 +33,12 @@ const isJoiningPractice = ref(false)
// Bot connection mode
const isVerifyingWebhook = ref(false)
const connectionMode = ref<'webhook' | 'polling'>('webhook')
// Polling is the documented default (BOTFIGHTS.md: "Use this if you didn't
// specify a mode — it's simpler and works from any machine") and is also
// the only mode the AI-answer option applies to — defaulting here means
// that section is visible immediately with zero clicks, not hidden behind
// picking a non-default mode first.
const connectionMode = ref<'webhook' | 'polling'>('polling')
const botSecret = ref('')
const botId = ref('')
const setupGuideCopied = ref(false)
@@ -562,7 +567,10 @@ const aiApiKey = ref('')
const aiConfigured = ref(false)
const aiSaving = ref(false)
const aiError = ref('')
const showAiSetup = ref(false)
// Expanded by default (not collapsed) — this is the whole point of the
// feature ("don't want to run a script?"), it needs to be immediately
// visible the moment poll mode is picked, not hidden behind another click.
const showAiSetup = ref(true)
async function saveAiConfig() {
if (!botId.value || !botSecret.value || !aiApiKey.value.trim()) return
@@ -1072,7 +1080,8 @@ function handleSignOut() {
<span class="font-mono text-[9px] px-1.5 py-0.5 border border-border text-text-muted">EASIEST</span>
</div>
<p class="font-mono text-[10px] text-text-muted leading-relaxed">
Your bot polls us. No public URL needed. Just keep it running.
Your bot polls us. No public URL needed. Just keep it running
or skip the script entirely and let BotFights answer with your own AI key.
</p>
</button>
</div>
+8 -1
View File
@@ -67,7 +67,14 @@ app.use('*', secureHeaders({
defaultSrc: ["'self'"],
scriptSrc: ["'self'", 'blob:', "'wasm-unsafe-eval'"],
styleSrc: ["'self'", "'unsafe-inline'", 'https://fonts.googleapis.com'],
imgSrc: ["'self'", 'data:', 'blob:'],
// https: (broad) is required, not optional: profile pictures come from
// nostr kind:0 metadata events — a URL the USER sets via their own
// client, hosted on whatever domain they picked. There is no central
// image host to allowlist for a decentralized identity system. Images
// can't execute script even from an untrusted origin, so this is the
// standard, safe CSP relaxation for user-supplied avatar URLs (unlike
// broadening script-src, which stays locked to 'self').
imgSrc: ["'self'", 'data:', 'blob:', 'https:'],
connectSrc: ["'self'", 'https://huggingface.co', 'https://*.huggingface.co', 'https://*.hf.co', 'https://cdn.jsdelivr.net', 'wss://relay.damus.io', 'wss://relay.nostr.band', 'wss://nos.lol'],
fontSrc: ["'self'", 'https://fonts.gstatic.com'],
workerSrc: ["'self'", 'blob:'],