Feature request: Opt‑in programmatic init API for first‑boot setup
Summary
Automating first‑boot configuration for CTFd currently requires driving the CSRF‑protected HTML form at /setup (cookie jar + nonce scraping). This is brittle for infrastructure automation (ECS/Fargate, Helm, etc.).
I'm proposing an opt‑in, disabled‑by‑default JSON API endpoint to perform initial setup in a secure, idempotent, and well‑documented way.
Current behavior (what automation must do today)
Because /setup is an HTML form guarded by CSRF and session cookies, a headless initializer must:
- GET
/setupwithout following redirects; capture cookies. - Parse the CSRF nonce from the HTML (hidden input
name="nonce"). - POST
application/x-www-form-urlencodedback to/setupwith fields includingnonce,ctf_name,name,email,password. - Treat a redirect away from
/setupas success; otherwise retry.
Example (Python stdlib; trimmed):
import re, json, urllib.request, urllib.error
class NoRedirect(urllib.request.HTTPRedirectHandler):
def redirect_request(self, *args, **kwargs): return None
opener = urllib.request.build_opener(NoRedirect())
base = "https://ctfd.example.com"
r = opener.open(urllib.request.Request(base + "/setup", method="GET"), timeout=10)
html = r.read().decode("utf-8", "ignore")
m = re.search(r'name=["\']nonce["\']\s+value=["\']([^"\']+)', html)
nonce = m.group(1)
form = urllib.parse.urlencode({
"nonce": nonce,
"ctf_name": "My CTF",
"name": "admin",
"email": "[email protected]",
"password": "StrongPass#1",
}).encode()
req = urllib.request.Request(base + "/setup", data=form, headers={
"Content-Type": "application/x-www-form-urlencoded"
}, method="POST")
try:
opener.open(req, timeout=15) # 30x redirect indicates configured
except urllib.error.HTTPError as e:
assert 300 <= e.code < 400
Pain points:
- HTML structure parsing is brittle; CSRF implementation details may change.
- Requires redirect control and cookie jar handling in every automation environment.
- Hard to document a “supported” way to automate; infra code ends up re‑implementing the same flow.
Proposal: Add an opt‑in first‑boot JSON endpoint
- Example Path:
POST /api/v1/init/setup - Disabled by default. Enable with explicit server config/env (e.g.,
CTFD_INIT_API_ENABLED=true). - Require a boot‑strap token (e.g.,
CTFD_INIT_API_TOKEN) provided via headerAuthorization: Bearer <token>. - Only usable when the instance is not yet configured. After success, the endpoint returns
409 AlreadyConfigured. - Idempotent: repeating with the same payload after success returns
200 OK+ a status object or409. - Rate‑limited and single‑use token option (
CTFD_INIT_API_TOKEN_SINGLE_USE=true) to reduce attack surface. - Clear audit logging on server: caller IP, outcome (created/skipped), redacted fields.
Request (JSON):
POST /api/v1/init/setup HTTP/1.1
Authorization: Bearer <one-time-token>
Content-Type: application/json
{
"ctf_name": "My CTF",
"admin": {
"name": "admin",
"email": "[email protected]",
"password": "StrongPass#1"
},
"ctf_description": "Internal training",
"theme": "core",
"ctf_timezone": "UTC",
"lang": "en",
"user_mode": "teams" // or "users"
}
Responses:
201 Created– setup completed now; body returns minimal config summary (no secrets).200 OK– already configured with matching state (idempotent).401 Unauthorized– missing/invalid token.403 Forbidden– endpoint disabled by config.409 Conflict– already configured (generic case).400 Bad Request– validation errors.
Example response (success):
{
"success": true,
"configured": true,
"ctf_name": "My CTF"
}
Security considerations:
- Disabled by default; clearly documented risk if enabled on public endpoints.
- Strong, random
CTFD_INIT_API_TOKEN; optionally time‑limited (CTFD_INIT_API_TOKEN_TTL). - Option to restrict to private networks (CIDR allowlist) or loopback only.
- Enforce password policy and input validation.
- Immediately disable the endpoint after successful configuration if
CTFD_INIT_API_DISABLE_ON_SUCCESS=true.
Operational guidance:
- Document official init flows: (1) HTML/CSRF form; (2) Opt‑in JSON API for automation.
- Provide a tiny example client (curl + token) to demonstrate usage.
- Log a clear warning if the endpoint is enabled.
Minimal alternative (even smaller change)
If adding a new endpoint is undesirable, consider allowing a JSON variant on /setup when a trusted bootstrap token header is present, e.g.:
- Header:
X-CTFd-Init-Token: <token> - Content-Type:
application/json - Bypass CSRF for first‑boot only when token matches, instance unconfigured.
This preserves the existing route while giving a supported path for infrastructure to initialize safely.
Why this helps
- Makes cloud‑native deployment (Fargate/Kubernetes) sane without brittle HTML scraping.
- Encourages a single, documented, testable init flow for maintainers and users.
- Reduces support burden from ad‑hoc automation scripts.
Backwards compatibility
- No change to existing
/setupUI flow. - New API is opt‑in and off by default.
Would this be more efficient than just having all configuration options in the env / config.ini?