You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Dependency advisories — override transitive undici to ^6.27.0
(GHSA-vxpw-j846-p89q) and ws to ^8.21.0 (GHSA-96hv-2xvq-fx4p), both high
severity, reached via discord.js. bun audit --audit-level=high is clean.
No raw error text to users — formatInteractionError no longer echoes
arbitrary error messages to Discord replies (could leak filesystem paths,
remote URLs, or git command lines); full errors stay in the logs.
Added
Typed CTFd errors — CtfdAuthError / CtfdApiError in core, thrown by the
CTFd client and matched via instanceof (with cause-chain unwrapping)
instead of brittle message-string sniffing.
Shared Discord reconciler — ensureChallengeThreads, textChannelsUnderCategory, and fetchThreadNames consolidate the
category-channel + per-challenge thread logic used by /ctf start, /ctf refresh, and the poller. Dedup includes archived threads.
RepoManager.countChallenges() — parallel repo walk counting .challenge-id markers; /ctf status now reports CTFd / Git / Discord counts.
Operator tooling — scripts/install-vps.sh hardened systemd installer, make verify-live smoke-test checklist, and grouped make help.
Changed
Bot requires SSH remotes — validateGitRemoteUrl({allowHttps}); the bot
rejects HTTPS git remotes (it runs non-interactively and can't supply
credentials). Behavior change for bot users on HTTPS remotes. The CLI
still allows HTTPS for interactive use.
/ctf status responsiveness — defers the reply, runs per-CTF cache load,
repo walk, and Discord fetch concurrently, and counts threads via a live API
fetch (restart-safe; no longer reports 0 from a cold gateway cache).
ensureChallengeThreads contract — requires a defined categoryId; the
poller guards an unset category so it can no longer create orphan channels at
the guild root.
Fixed
Duplicate threads after auto-archive — the dedup set now includes archived
threads, so a thread that auto-archived (7-day default) is not recreated on the
next refresh/poll.
Partial-init visibility — /ctf start surfaces a shortfall warning when
some challenge threads fail to create, instead of reporting success.
Removed
docs/migration.md (pre-v0.3.0 upgrade path) and docs/release.md
(internal release checklist).
Fixed (anti-slop cleanup pass)
Slug-collision guard correctness — .challenge-id now written with O_EXCL;
EACCES/EIO/EISDIR on read no longer silently bypass the collision check.
Lock acquire error classification — non-EEXIST errors (EACCES, ENOSPC,
EROFS, EISDIR) surface immediately instead of busy-looping until timeout.
Bot commit-push lock cascade — commitAndPush now acquires + releases
the FileLock per closure; chained pushes no longer hit a 5s lock-acquire
timeout when commits land within the debounce window.
Poller resilience — reschedule moved into finally so a synchronous
throw cannot kill polling; each thread-create is wrapped per-iteration so one
bad challenge no longer poisons forward progress.
Poller TOCTOU — meta is plumbed into createThreadsForNewChallenges
instead of refetched via getActiveCTF (which would throw if the CTF was
archived between the loop start and the call).
ctf-start ordering and cleanup — guild check runs before lock.acquire;
Discord channel creation block is wrapped so a mid-loop failure deletes the
partial category instead of leaving orphans.
ctf-start download error visibility — download phase failure now appears
in the user-facing reply instead of being silently logged.
CTFd auth-redirect detection — isAuthRedirect parses the location as a
URL and checks pathname/searchParams.has("next") instead of brittle
substring matches (?nextpage= no longer false-positives; CTFd installs at
non-root paths like /ctf/login are correctly detected).
DNS-rebinding mitigation TTL — validatedOrigins cache is bounded to 60s
so a one-time public-IP lookup cannot defeat the SSRF guard forever.
CLI password prompt SIGINT — Ctrl-C / Ctrl-D in the raw-mode prompt now
restores terminal state instead of leaking raw mode.
CLI writeup editor exit — non-zero editor exit (e.g. :cq) logs a warning
instead of producing an unhandled promise rejection.
CLI refresh auth-error hint — adds a session-expiry hint when the error
looks auth-related.
Bot active-CTF resolution — getActiveCTF no longer silently falls back
to active[0]. Behavior change:/ctf-refresh and /ctf-archive
invoked outside a challenge thread now require currentCTF to be set (via /ctf-use or per-thread context); previously they would pick the first
active CTF found. Intended as a safety improvement — prevents writes to a
random CTF when the caller's context fails to resolve.
Fixed (follow-up cleanup)
/claim race-safety — team-mapping and claim writes for the same CTF
now run under a per-CTF FileLock. Concurrent /claim invocations no longer
drop the loser's team mapping or interleave their writes.
/ctf start ordering — commit happens first, then registerCTF, then
push. A push failure no longer leaves a remote scaffold without a local
record; the user can recover with a manual git push.
Auth-resolver input narrowed — resolveAuthAndClient now accepts a
structural AuthInput ({url, name?, token, sessionCookie, username})
instead of a full CTFMeta. init and ctf start no longer fabricate
placeholder repo: "" / repoPath: "" / status: "active" fields.
Shared download-summary formatter — formatDownloadSummary() in core
produces the headline/lines/overflow shape consumed by both CLI init and
bot /ctf start.
Shared Discord rate-limit constant — DISCORD_THREAD_CREATE_DELAY_MS
exported from bot/index.ts and used by both poller and ctf-start.
Added
Persistent active CTF pointer for CLI — ctfd use <name> now writes currentCTF in global config and command resolution honors it.
CTF Discord routing metadata — CTF meta now stores Discord guild/category
identifiers so poller and thread commands can target the correct CTF context.
Download summary plumbing — downloadChallengeFilesForCTF() now returns
downloaded/skipped counts with skip reasons; callers surface/report skips.
Challenge slug-collision guard — RepoManager now writes and validates
per-directory .challenge-id markers to prevent silent overwrite when
different challenges map to the same slug path.
Changed
Poll scheduling model — bot poller now runs serialized recursive ticks
(no overlapping setInterval async executions).
CLI and bot repo path consistency — CLI init/join now use repoPathFor(ctfName) under data root, matching bot behavior.
Service install semantics — make service-install no longer errors on an
existing /etc/ctfd-warboard/bot.env; deployment docs updated accordingly.
Challenge markdown file links — generated links now strip query/hash
components (e.g. token query parameters) before writing to git.
Fixed
Critical lock race — FileLock.acquire() now uses atomic wx lockfile
creation, preventing concurrent contenders from both acquiring a fresh lock.
CLI credential durability — init persists the actual username for
password-based auth; refresh now uses shared auth resolver and persists
refreshed session cookies.
CLI writeup editor flow — ctfd writeup now waits for editor process exit.
Bot /solve CTF mismatch — solve now resolves CTF from thread context and
requires the challenge to exist in the active CTF cache before submission.
Cross-process git safety for bot writes — bot commit/push path now acquires
the same file lock family used by CLI operations.
/ctf start race window — per-CTF lock is acquired before Discord/channel
creation and initialization workflow.
Poller channel misrouting — new-thread creation is scoped to stored
guild/category instead of matching category names across all guilds.
Cache diff blind spot — challenge file list changes are now detected as
updates.
Validation side effect — removed console.warn from URL validation.