Releases: gl0bal01/ctfd-warboard
Releases · gl0bal01/ctfd-warboard
v0.4.0
Security
- Dependency advisories — override transitive
undicito^6.27.0
(GHSA-vxpw-j846-p89q) andwsto^8.21.0(GHSA-96hv-2xvq-fx4p), both high
severity, reached viadiscord.js.bun audit --audit-level=highis clean. - No raw error text to users —
formatInteractionErrorno 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/CtfdApiErrorin core, thrown by the
CTFd client and matched viainstanceof(withcause-chain unwrapping)
instead of brittle message-string sniffing. - Shared Discord reconciler —
ensureChallengeThreads,
textChannelsUnderCategory, andfetchThreadNamesconsolidate 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-idmarkers;/ctf statusnow reports CTFd / Git / Discord counts.- Operator tooling —
scripts/install-vps.shhardened systemd installer,
make verify-livesmoke-test checklist, and groupedmake 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 statusresponsiveness — 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).ensureChallengeThreadscontract — requires a definedcategoryId; 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 startsurfaces a shortfall warning when
some challenge threads fail to create, instead of reporting success.
Removed
docs/migration.md(pre-v0.3.0 upgrade path) anddocs/release.md
(internal release checklist).
Fixed (anti-slop cleanup pass)
- Slug-collision guard correctness —
.challenge-idnow written withO_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 —
commitAndPushnow 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
finallyso a synchronous
throw cannot kill polling; each thread-create is wrapped per-iteration so one
bad challenge no longer poisons forward progress. - Poller TOCTOU —
metais plumbed intocreateThreadsForNewChallenges
instead of refetched viagetActiveCTF(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 —
isAuthRedirectparses the location as a
URL and checkspathname/searchParams.has("next")instead of brittle
substring matches (?nextpage=no longer false-positives; CTFd installs at
non-root paths like/ctf/loginare correctly detected). - DNS-rebinding mitigation TTL —
validatedOriginscache 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 —
getActiveCTFno longer silently falls back
toactive[0]. Behavior change:/ctf-refreshand/ctf-archive
invoked outside a challenge thread now requirecurrentCTFto be set (via
/ctf-useor 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)
/claimrace-safety — team-mapping and claim writes for the same CTF
now run under a per-CTFFileLock. Concurrent/claiminvocations no longer
drop the loser's team mapping or interleave their writes./ctf startordering — commit happens first, thenregisterCTF, then
push. A push failure no longer leaves a remote scaffold without a local
record; the user can recover with a manualgit push.- Auth-resolver input narrowed —
resolveAuthAndClientnow accepts a
structuralAuthInput({url, name?, token, sessionCookie, username})
instead of a fullCTFMeta.initandctf startno longer fabricate
placeholderrepo: ""/repoPath: ""/status: "active"fields. - Shared download-summary formatter —
formatDownloadSummary()in core
produces the headline/lines/overflow shape consumed by both CLIinitand
bot/ctf start. - Shared Discord rate-limit constant —
DISCORD_THREAD_CREATE_DELAY_MS
exported frombot/index.tsand used by bothpollerandctf-start.
Added
- Persistent active CTF pointer for CLI —
ctfd use <name>now writes
currentCTFin 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 —
RepoManagernow writes and validates
per-directory.challenge-idmarkers 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 overlappingsetIntervalasync 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-installno 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 atomicwxlockfile
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 writeupnow waits for editor process exit. - Bot
/solveCTF 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 startrace 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.warnfrom URL validation.
v0.3.0 — community readiness: operator surface, data-root, parity, fixes
Full community-readiness release. All Phases 2–6 of the readiness plan land together per the v0.3.0 ADR (atomic Phase 2 + 2.5 constraint; Phases 3–6 within the same release).
Upgrading from v0.2.x? See docs/migration.md. Existing operators on systemd must
mvrepos to/var/lib/ctfd-warboard/repos/(or accept the auto-WARN withmvinstructions) and re-runmake service-install.
Operator surface (Phase 2)
- Makefile at repo root with 17 targets — single entrypoint for install / check / bot / service-* / migrate-data-root.
.env.example+scripts/ctfd-bot.env.exampledocumenting all bot env vars (no real secrets).docs/deployment.md— full VPS setup: dedicated user, FHS layout, env file, systemd install, journald, SSH key, backups, log rotation, secret rotation, mixed bot+CLI access.
Data root + hardened systemd (Phase 2.5, atomic)
packages/core/src/paths.ts— shared resolver:dataRoot(),repoPathFor(),lockPathFor(),assertDataRootWritable(). HonorsCTFD_WARBOARD_HOME; absolute-path validation;mkdir -pmode0700; path-traversal rejection via allowlist regex; boot-time writability check (FATAL + exit 1 before Discord connect).- Bot + CLI share data root via
CTFD_WARBOARD_HOME. BotCONFIG_DIRderived fromdataRoot(). Botctf-start.tsandctf-join.tsuserepoPathFor(ctfName)instead of cwd-basedresolve(ctfName). ConfigManagerlazy-oncemeta.repoPathmigration — rewrites persisted CTFMeta entries whoserepoPathfalls outsidedataRoot(); WARN log with old→new +mvinstruction (emitted only after rename succeeds); idempotent.make migrate-data-root— triggers migration without starting the bot.- Hardened systemd unit (
scripts/ctfd-bot.service) —User=ctfd-warboard,EnvironmentFile=,Environment=CTFD_WARBOARD_HOME=/var/lib/ctfd-warboard,ProtectSystem=strict,ReadWritePaths=/var/lib/ctfd-warboard,NoNewPrivileges=true,PrivateTmp=true,ProtectHome=true. No inline secrets. make service-installcode-level gate — refuses to install the strict unit ifpackages/core/src/paths.tsis absent (filesystem stat).- Cross-process lock unification — bot and CLI both resolve lock paths through
lockPathFor(); concurrent ops on sharedCTFD_WARBOARD_HOMEserialize correctly.
Bot ↔ CLI parity (Phase 3)
downloadChallengeFilesForCTFin@ctfd-warboard/core— shared download orchestration helper.- Bot
/ctf startnow downloads CTFd challenge files (identical layout toctfd init), with Discord interactioneditReplyprogress edits every 10 challenges. Downloads run inside the per-ctfNameFileLock, before the git transaction. - CLI
init.tsnow also uses the shared helper — single source of truth.
Functional fixes (Phase 4)
- CLI
ctfd claimusesresolveChallengeforcategory/challengeresolution (web/sql-injectionnow resolves correctly to categoryweb). - CLI
--versionreads dynamically frompackages/cli/package.json. resolveAuthAndClient(meta, opts?)in@ctfd-warboard/core— shared auth helper. Priority:meta.token→meta.sessionCookie(verified; 401 falls through) → username + password. Bot/ctf start, bothelpers.ts, CLIsolve, CLIinitall migrated.loginWithCredentialsdirect callsites inpackages/botandpackages/cliremoved.- CLI
ctfd solvenow supports all auth types (was token-only). helpers.tserror rewrap narrowed — only emits "No credentials found" when no auth method exists; otherwise re-throws original error withcause(DNS/TLS/SSRF surface correctly).
CI + release docs (Phase 5)
- CI Bun version pinned to
1.3.13in.github/workflows/ci.yml;.tool-versionsadded for local reproducibility. - CI runs
make check(single entrypoint matches local). docs/release.md— release checklist with concrete shell commands.
README (Phase 6)
- New sections: "5-minute local bot smoke test", "Production install on Ubuntu with systemd", "How secrets are stored", "Known limitations", "Troubleshooting". Cross-links to
docs/deployment.md,docs/migration.md,docs/release.md.
Verification
- 239 tests pass / 0 fail across 28 files.
bun run lint,bun run typecheck,bun audit --audit-level=high→ exit 0.systemd-analyze verify scripts/ctfd-bot.service→ exit 0.- Acceptance greps:
resolve(ctfName)\|resolve(repoName)inpackages/bot/src→ empty;loginWithCredentialsinpackages/bot packages/cli→ empty.
Known limitations
- VM smoke test under the strict systemd unit is deferred — operators are encouraged to exercise the hardened unit before mass adoption and append any required additional
ReadWritePaths=entries. - Several NIT/MEDIUM follow-ups from the v0.3.0 code review (memoize
mkdirSync, narrowauth-resolverre-login to also cover 403, ctf-start scaffold-before-lock, etc.) are filed as future work.
Full Changelog: v0.2.1...v0.3.0
v0.2.1 — Phase 1 community readiness blockers
Security and reliability patch. Adopt this release before v0.3.0 — it ships the CVE fix and git-sync correctness fixes independently of the larger v0.3.0 runtime rewrite.
Security
- Pinned
simple-gitto exact3.36.0across all three workspaces (core, cli, bot), resolving GHSA-hffm-xvc3-vprc (ReDoS in<3.36.0).
Fixed
- Bot git-sync push-before-commit ordering corrected across all 8
commitAndPushcallsites inpackages/bot. Push is now chained to the latest debounced commit viaenqueuePushAfter, so a push can never race ahead of its commit. - Push silently dropped on back-to-back
commitAndPushcalls (HIGH): removedpushPendingguard fromenqueuePush; the FIFO queue serialises pushes andenqueuePushAfter's commit-level debounce handles coalescing. drain()skipping chained push on SIGTERM (MEDIUM):drain()now flushespendingPushFnalongsidependingCommitFn./ctf startinitial repo flow is now an atomic locked transaction: a per-ctfNameFileLockis acquired before init, ensuring concurrent invocations for the same CTF cannot double-initialise the repository.
Internal
enqueueTransaction(fns: AsyncFn[])added toGitSyncQueuein@ctfd-warboard/core. Executes an ordered sequence of async operations inside the queue, propagating the first error and aborting on throw.
Verification
- 209 tests pass / 0 fail.
bun audit --audit-level=high→ 0 vulnerabilities.
Full Changelog: v0.2.0...v0.2.1
v0.1.0
First release
Rate-limited CTFd parser with CLI + Discord bot for small-team CTF collaboration. Every action auto-syncs to a shared private git repo.
Features
- Rate-limited API client — 1 req/sec with exponential backoff on 429s
- Cache-first — fetches once, polls for changes every 10 minutes
- Full challenge details — descriptions, files, hints fetched and stored
- File downloads — challenge attachments downloaded during init
- Git-native collaboration — claims, solves, notes auto-commit to shared repo
- Discord bot — slash commands for team coordination (
/ctf start,/claim,/solve,/note,/writeup) - CLI — terminal workflow with
ctfd init,ctfd claim,ctfd solve, etc. - Multi-CTF — manage multiple competitions simultaneously
- Private channels — Discord CTF channels are private by default
- Direct CTFd links — challenge threads and markdown include links to the live challenge
Authentication
- API token auth (
--tokenorCTFD_TOKEN) - Username/password auth (
--username/--passwordorCTFD_USERNAME/CTFD_PASSWORD) - CSRF nonce handling for CTFd instances requiring session auth
- Lazy CSRF token fetch for flag submission
Security
- Path traversal protection
- SSRF blocking for private IPs
- Credential isolation from git repos
- bot.json with 0600 permissions