Environment Variables
This page consolidates all environment variables across the three CTFHive
components. Set them in .env files (loaded by python-dotenv) or directly
in the process environment. The detailed per-area configuration walkthrough is
in Getting Started → Configuration.
Production secrets
SECRET_KEY, ADMIN_KEY, ENCRYPTION_KEY, PROVISION_AUDIT_SECRET,
and all Stripe/Linode keys must be replaced with strong random values
before going live. Their defaults are intentionally obvious placeholders.
CTFHive app (ctfapp)
Source: ctfapp/config.py
Core secrets
| Variable |
Default |
Required |
Description |
SECRET_KEY |
change-me-in-production |
Yes |
Flask session secret. Rotate requires all sessions to be invalidated. |
ADMIN_KEY |
change-me-admin-key |
Yes |
HMAC key for flag derivation (SHA3-256) and the application audit chain (SHA-256). Changing this invalidates all derived flags and breaks audit chain verification. |
ENCRYPTION_KEY |
change-me-encryption-key |
Yes |
Key for XChaCha20-Poly1305 column encryption (password hashes, IPs, VPN keys, etc.). Falls back to SECRET_KEY if unset. |
APP_ENV |
development |
No |
development | production | testing. Selects config class. |
Database and cache
| Variable |
Default |
Description |
DATABASE_URL |
sqlite:///ctfapp.db |
SQLAlchemy connection string. Use postgresql+psycopg2:// in production. |
REDIS_URL |
redis://localhost:6379/0 |
Redis connection string used for caching, rate limiting, and flag cache. |
Application identity
| Variable |
Default |
Description |
CTF_NAME |
GIDK CTF |
Displayed in the browser title and header. |
CTF_DESCRIPTION |
(GrizzHacks8 description) |
Shown on the homepage. |
CTF_LOGO |
img/logo.png |
Relative path to logo image. |
CTF_FAVICON |
favicon.svg |
Relative path to favicon. |
SITE_URL |
http://localhost:5000 |
Canonical URL; used in email links and CSP. |
BASE_DOMAIN |
(empty) |
Apex domain for this tenant (e.g. abc123.ctfhive.us). Set by the provisioner. |
DEFAULT_FLAG_PREFIX |
GRIZZ |
Prefix for derived flags, e.g. GRIZZ{...}. |
PREFERRED_URL_SCHEME |
http |
http | https. Set to https in production. |
Admin bootstrap
| Variable |
Default |
Description |
ADMIN_USERNAME |
admin |
Username for the auto-created admin account. |
ADMIN_PASSWORD |
GrizzAdmin!2025 |
Password for auto-created admin. Change immediately. |
ADMIN_EMAIL |
admin@grizzhacks8ctf.local |
Email for auto-created admin. |
BOOTSTRAP_ADMIN_IN_PRODUCTION |
false |
Set true to run admin bootstrap in production environments. |
Session and cookie security
| Variable |
Default |
Prod default |
SESSION_COOKIE_SECURE |
false |
true |
SESSION_COOKIE_SAMESITE |
Lax |
Lax |
Rate limiting
Use Redis in production
The default RATELIMIT_STORAGE_URI=memory:// counts requests per-process.
Under multiple Gunicorn workers, each worker has its own counter, so the
effective limit becomes workers × configured_limit. Set
RATELIMIT_STORAGE_URI=redis://localhost:6379/1 to share counts correctly.
| Variable |
Default |
Description |
RATELIMIT_STORAGE_URI |
memory:// |
Rate limit backend. Use redis://... in production. |
LOGIN_RATE_LIMIT |
10 per minute |
Login endpoint limit. |
REGISTER_RATE_LIMIT |
5 per minute |
Registration endpoint limit. |
PASSWORD_RESET_RATE_LIMIT |
5 per hour |
Password reset limit. |
TEAM_JOIN_RATE_LIMIT |
10 per minute |
Team join endpoint limit. |
ADMIN_SENSITIVE_RATE_LIMIT |
30 per minute |
Admin sensitive actions limit. |
FLAG_WRONG_LIMIT |
3 |
Wrong submissions before lockout. |
FLAG_LOCKOUT_SECONDS |
30 |
Lockout duration after wrong-submission limit. |
Email
| Variable |
Default |
Description |
MAILTRAP_API_KEY |
(empty) |
Mailtrap API key. Email features (verification, password reset) are silently disabled when this is empty. |
MAIL_SENDER |
no-reply@grizzhacks8ctf.us |
From address for outbound email. |
EMAIL_VERIFICATION_ENABLED |
false |
Require email verification on registration. |
| Variable |
Default |
Description |
SECURE_HEADERS_ENABLED |
true |
Enable security response headers. |
ENABLE_HSTS |
false |
Add Strict-Transport-Security header. Enable in production with TLS. |
HSTS_SECONDS |
31536000 |
max-age value in HSTS header. |
CONTENT_SECURITY_POLICY |
(strict default) |
Full CSP header value. Overridable. |
TRUST_PROXY |
false |
Trust X-Forwarded-* headers (set true behind Caddy/nginx). |
PROXY_FIX_X_FOR |
1 |
Number of trusted X-Forwarded-For hops. |
Labs and containers
| Variable |
Default |
Description |
LAB_ENABLED |
false |
Enable Docker container/lab spawning. Requires Docker access. |
LAB_DOCKER_HOST |
(host socket) |
Docker daemon socket or TCP address. |
LAB_SUBNET_POOL_CIDR |
10.200.0.0/16 |
Pool from which per-team Docker bridge subnets are carved. |
LAB_SUBNET_PREFIX |
24 |
Prefix length for each team subnet. |
LAB_VPN_PEER_POOL_CIDR |
10.50.0.0/24 |
Pool for WireGuard peer IPs. |
LAB_VPN_PLAYERS_PER_TEAM |
4 |
Max WireGuard peers per team per lab. |
LAB_APPLY_IPTABLES |
false |
Apply iptables forwarding rules for team isolation. |
LAB_WG_INTERFACE |
wg0 |
WireGuard interface name on the host. |
LAB_ISSUE_WG_CONFIG |
false |
Issue WireGuard configs to players. |
LAB_WG_ENDPOINT |
vpn.example.com |
Public WireGuard endpoint address:port. |
LAB_WG_LISTEN_PORT |
51820 |
WireGuard listen port. |
LAB_WG_SERVER_PUBLIC_KEY |
(empty) |
Server's WireGuard public key (injected into client configs). |
LAB_REQUIRE_PINNED_IMAGES |
false |
Require image@sha256:... digest pinning. Recommended true in production. |
LAB_ALLOW_BASENAME_IMAGE_FALLBACK |
true |
Allow tag-miss fallback to same-named image. Set false to prevent supply-chain confusion. |
LAB_PACKET_CAPTURE_ENABLED |
false |
Enable per-lab packet capture recording. |
LAB_PACKET_CAPTURE_TOOL |
tcpdump |
Capture tool on the host. |
LAB_PACKET_CAPTURE_SNAPLEN |
256 |
Packet snaplen in bytes. |
LAB_PACKET_CAPTURE_MAX_PACKETS_PER_CHUNK |
250 |
Packets per stored chunk. |
LAB_PACKET_CAPTURE_MAX_RAW_BYTES_PER_CHUNK |
131072 |
Raw bytes per chunk (128 KB). |
LAB_PACKET_CAPTURE_MAX_CHUNKS_PER_LEASE |
256 |
Max chunks stored per lease. |
INSTANCE_TTL_SECONDS |
7200 |
Default lab instance lifetime in seconds (2 hours). |
INSTANCE_DNS_DOMAIN |
(empty) |
Wildcard DNS domain for instance URLs. Empty shows localhost in UI. |
CTF_DEPLOY_MODE |
local |
local | remote. |
DISPATCH_INTERNAL_URL |
http://localhost:5001 |
URL of the lab dispatcher service. |
DISPATCH_ADMIN_TOKEN |
change-me-dispatch-token |
Auth token for dispatcher API. |
DISPATCH_USE_REMOTE |
true |
When false, use local Docker fallback instead of remote dispatcher. |
DISPATCH_PREFER_LOCAL |
true |
Prefer local Docker when both are available. |
LAB_WORKER_ID |
local-worker |
This worker's ID for drain management. |
LAB_WORKER_MAX_CPU_UNITS |
16000 |
CPU capacity for scheduling. |
LAB_WORKER_MAX_MEMORY_MB |
32768 |
Memory capacity (MB) for scheduling. |
Container registry
| Variable |
Default |
Description |
REGISTRY_HOST |
(empty) |
Registry hostname (e.g. registry.ctfhive.us). |
REGISTRY_USER |
(empty) |
Registry username. |
REGISTRY_PASS |
(empty) |
Registry password. |
Event timing
| Variable |
Default |
Description |
EVENT_STARTS_AT |
2025-03-28T12:00:00+00:00 |
ISO 8601 datetime when the CTF opens. |
EVENT_ENDS_AT |
2025-03-29T12:00:00+00:00 |
ISO 8601 datetime when the CTF closes. |
Theme and UI
| Variable |
Default |
Description |
DEFAULT_THEME |
minimal |
Default theme ID. |
DEFAULT_BG_ANIMATION |
matrix |
Default background animation. |
Miscellaneous
| Variable |
Default |
Description |
MAX_CONTENT_LENGTH |
33554432 (32 MB) |
Maximum request body size. |
CHALLENGES_ROOT |
{repo_root}/challenges |
Path to local challenge directory. |
CTFHive control plane (ctrlapp)
Source: CTF_Saas_CTRL_Pane/ctrlapp/config.py
| Variable |
Default |
Description |
SECRET_KEY |
dev-insecure-change-me |
Flask session secret for the control plane. |
BASE_DOMAIN |
ctfhive.us |
Apex domain used to construct tenant subdomains (e.g. {slug}.ctfhive.us). |
STRIPE_SECRET_KEY |
(empty) |
Stripe secret key (sk_live_... or sk_test_...). |
STRIPE_PUBLISHABLE_KEY |
(empty) |
Stripe publishable key (pk_live_...). |
STRIPE_WEBHOOK_SECRET |
(empty) |
Webhook signing secret from the Stripe dashboard (whsec_...). |
PROVISION_AUDIT_SECRET |
audit-dev-secret |
HMAC key for the provisioner JSONL audit chain. See Audit Chain. |
LINODE_API_TOKEN |
(empty) |
Linode API token for server provisioning. Leave empty to use FakeExecutor (dry-run mode). |
LINODE_REGION |
us-east |
Linode datacenter region for new servers. |
LINODE_PLAN |
g6-standard-2 |
Linode plan / instance type. |
LINODE_IMAGE |
linode/debian12 |
Base OS image for provisioned servers. |
Stripe price IDs
The control plane codebase references STRIPE_PRICE_SOLO, STRIPE_PRICE_STANDARD,
and STRIPE_PRICE_PRO in the pricing configuration (ctrlapp/pricing.py).
These are Stripe Price object IDs (e.g. price_...) created in the Stripe
dashboard and set in the environment before enabling live billing.
Provisioner
Source: provisioner/ package. Variables read at runtime by scripts.
| Variable |
Default |
Description |
PROVISION_AUDIT_SECRET |
(same as control plane) |
HMAC key for the JSONL audit chain written during provisioning runs. |
LINODE_API_TOKEN |
(empty) |
Used by the real LinodeExecutor to call the Linode API. |
The provisioner is invoked by the control plane's ProvisionService and
inherits the control plane's environment. In dry-run mode (FakeExecutor),
no Linode API calls are made and no actual server is created.
Quick-start minimal .env
# CTFHive app — minimum for local development
SECRET_KEY=dev-secret-change-me
ADMIN_KEY=dev-admin-key-change-me
ENCRYPTION_KEY=dev-enc-key-change-me
DATABASE_URL=sqlite:///ctfapp.db
REDIS_URL=redis://localhost:6379/0
APP_ENV=development
# CTFHive control plane — minimum for local development
SECRET_KEY=ctrl-dev-secret
PROVISION_AUDIT_SECRET=audit-dev-secret
# Leave STRIPE_* and LINODE_* empty → fake/dry-run mode