GZ::CTF is a full‑stack, production‑ready CTF platform for competitions and practice, designed for extensibility (dynamic/static challenges, containers, dynamic flags), observability (health/metrics/traces), and operability (rate limiting, RBAC, pluggable captcha/storage/cache). Backend is ASP.NET Core 9 + EF Core (PostgreSQL), frontend is React 19 + Vite with real‑time SignalR.
- Exercise-related models/features are out of scope for now. Ignore those files when implementing features.
- Be conservative with database schema changes. If logic can be done in the frontend (calculation/UI), avoid adding backend fields or endpoints.
- Prefer existing extension points (RateLimiter, Captcha, Storage, Telemetry, SignalR) and use inline JSON fields (
Tags/Hints/Divisions) to keep schema flexible and avoid premature hardening. - Follow role-based access patterns: use
[RequireUser],[RequireMonitor],[RequireAdmin],[RequireAdminOrToken]attributes consistently. - TCP-over-WebSocket proxy (
ProxyController) enables browser access to challenge containers whenContainerPortMappingType.PlatformProxyis configured.
- Backend
src/GZCTF(ASP.NET Core):- Entry:
Program.cswires startup viaExtensions/Startup/*(web host, DB, storage, cache/SignalR, identity, telemetry, services, web services). - Middlewares & maps:
Extensions/AppExtensions.cssets routing, rate limit, auth, request logging, health/metrics, SignalR hubs at/hub/{user|monitor|admin}, static/cachedindex.htmlviaUseIndexAsync()and custom favicon viaUseCustomFavicon(). - Data: EF Core PostgreSQL is mandatory. App exits if
ConnectionStrings:Databasemissing (DatabaseExtension.ConfigureDatabase). - Caching: Redis optional; when absent falls back to in-memory (
ConfigureCacheAndSignalR). - Storage: custom storage providers (local disk and S3) selected via connection string; default forced to
disk://path=./files(StorageExtension). - Telemetry: Health on
/healthzand Prometheus/metricsare served only on metrics port3000(Server.MetricPortandTelemetryExtension). App serves HTTP on8080. - Auth & roles: IdentityCore with cookies; use attributes
RequireUser,RequireMonitor,RequireAdmin,RequireAdminOrToken(Middlewares/PrivilegeAuthentication.cs). - Rate limiting: Global sliding window + named policies (
Middlewares/RateLimiter.cs), disabled byDisableRateLimit=true. - i18n:
Resources/withIStringLocalizer<Program>; invalid model state returns JSON viaInvalidModelStateHandler. - SignalR patterns: Strongly-typed hubs (
AdminHub,MonitorHub,UserHub) with client interfaces (IAdminClient,IMonitorClient,IUserClient) for real-time notifications. Each hub validates permissions and groups clients by game ID. - Container proxy:
ProxyControllerhandles TCP-over-WebSocket for challenge access whenContainerPortMappingType.PlatformProxyis set. Supports Kubernetes with traffic capture capabilities.
- Entry:
- Frontend
src/GZCTF/ClientApp(React + Mantine + Vite):- Dev server on
63000with proxy to backend; configure backend URL viaVITE_BACKEND_URL(defaults tohttp://localhost:8080) invite.config.mts. - Build outputs to
ClientApp/buildand is copied to backendwwwrootduringdotnet publish. - SpaProxy: Enabled via
Microsoft.AspNetCore.SpaProxyin development; auto-starts Vite dev server when runningdotnet run.
- Dev server on
- Identity & users
UserInfoextendsIdentityUser<Guid>;Rolestored as int, requires confirmed email. One‑to‑manyUserInfo -> Submission(FK set null on delete). Many‑to‑many withTeamfor membership.
- Teams and games
Teamhas manyMembers(users) and an ownerCaptain. Many‑to‑manyTeam <-> GameviaParticipation(join entity) to carry team‑in‑game state.GameaggregatesGameEvents,GameNotices,GameChallenges,Submissions,Participations, andDivisions(new dedicated table).Divisionsentity manages division-specific configs including challenge visibility and scoring rules.
- Divisions & challenge configs
Divisions: Dedicated table for multi-division support withDivisionChallengeConfigjoin entity for per-division challenge configuration.DivisionChallengeConfig: Manages per-division challenge settings (visibility, deadline, scoring).Participationreferences aDivision; supports division-scoped team participation and scoring.
- Participation and instances
Participation(PK: generated) joinsTeamandGame; holdsInstances(challenge instances for the team),Submissions,Members(UserParticipation), optionalWriteup, and optionalDivisionId.- Many‑to‑many
Participation <-> GameChallengeviaGameInstance(composite key).GameInstancemay reference a runningContainer(1‑1) and aFlagContext(dynamic flag) and is auto‑included withChallenge.
- Challenges & training
GameChallenge(derivesChallenge) relates toGame, hasFlags,Submissions, optionalAttachmentandTestContainer;Hintsstored as JSON; indexed byGameId. Per-challengeDeadlinefield for submission cutoff.FirstSolves: Tracks first blood for each challenge per-division or globally (team, user, submission time).
- Submissions & flags
Submission.Statuspersisted as string (compat/readability); navigations toTeam,User,GameChallengeauto‑included for audit/monitor views.FlagContextmay reference anAttachment(SetNull on delete) to support static/dynamic flag sources.
- Storage and attachments
Attachment->LocalFile(SetNull on delete) separates metadata from blob storage; integrates with pluggable storage via hash paths.
- Events, notices, content
GameEventandGameNoticestore structuredValuesas list JSON for schema‑light extensibility;GameEventlinks optionalTeamandUser.Post(announcements) has optionalAuthor(SetNull) andTagsas list JSON.
- Auxiliary tables
UserParticipationcomposite key(GameId, TeamId, UserId)accelerates membership queries.ApiTokenhasCreator(Restrict on delete).DataProtectionKeysfor ASP.NET DataProtection.
Design notes (flexibility vs performance)
- JSON converters for
List<string>/HashSet<string>store tags/hints/divisions inline (avoid join tables; easy to evolve). - Widespread
Navigation.AutoInclude()on hot paths improves DX;QuerySplittingBehavior.SplitQueryis enabled to avoid cartesian explosion when including graphs. - Delete behaviors prefer
SetNull/NoActionto preserve audit trails and prevent cascade storms across instances/submissions. - Many‑to‑many bridges (e.g.,
Participation,GameInstance) carry extra state and keep write paths efficient; Exercise-related bridges are currently ignored.
- Controllers return JSON
RequestResponseon errors; model validation is centralized. Keep URLs lowercase (AddRouting(LowercaseUrls=true)). - Use SignalR endpoints
/hub/user,/hub/monitor,/hub/admin; Vite dev proxy forwards/hubas WebSocket. - Permissions: Use
[RequireUser],[RequireMonitor],[RequireAdmin],[RequireAdminOrToken]attributes. New permissions:RequireReview(submission review) andAffectDynamicScore(dynamic scoring). - Captcha pluggable via
CaptchaConfig.Provider:HashPow(default) orCloudflareTurnstile(Extensions/CaptchaExtension.cs). - Storage connection string prefixes:
disk://(forced default),aws.s3://,minio.s3://,azure.blobs://. Self-maintenance storage ensures blob cleanup and consistency. - Environment configuration prefix:
GZCTF_(env vars override config). - Role hierarchy:
Admin(3) >Monitor(1) >User(0) >Banned(-1). FrontendWithRolecomponent usesRoleMapfor access control. - Container management: Docker (
DockerManager) and Kubernetes (KubernetesManager) support with K3s/MinIO integration. UseIContainerManagerinterface for consistency. - Task Status: Enum includes
Success,Failed,Pending,Running,Unhealthy,Degradedstatuses for container/service health. - Divisions API: Endpoints for game-scoped division management with challenge configs. Division affects challenge visibility, scoring, and deadline enforcement.
- FirstSolves tracking: Separate table for first-blood metadata (team, user, timestamp); used for bonus scoring and statistics.
- TypeScript API generation: Run
pnpm genapiin ClientApp to regenerate types from OpenAPI spec at/openapi/v1.json. - Repository pattern: All data access through
IRepository<T>interfaces withRepositoryBaseimplementation. - Logging: Use
logger.SystemLog(message, status, level)for structured logging withTaskStatusenum. - Error handling: Return
RequestResponseobjects with localized messages; useIStringLocalizer<Program>for i18n.
- Prereqs: .NET 9 SDK, Node 24+,
pnpm. - Single-command dev (SpaProxy auto-starts Vite):
dotnet run --project src/GZCTF/GZCTF.csproj- Launch profile sets
ASPNETCORE_ENVIRONMENT=Developmentand enablesMicrosoft.AspNetCore.SpaProxy(seeProperties/launchSettings.json); Vite dev runs on63000and proxies to backend.
- Manual dual-terminal (fallback if SpaProxy is disabled):
- Backend API:
dotnet run --project src/GZCTF/GZCTF.csproj - Frontend dev:
cd src/GZCTF/ClientApp && pnpm install && pnpm dev
- Backend API:
- Build & publish (includes SPA build):
dotnet publish src/GZCTF/GZCTF.csproj -c Release -o publish- Run:
dotnet ./publish/GZCTF.dll
- Tests (xUnit + coverlet):
dotnet test src/GZCTF.Test/GZCTF.Test.csproj -v minimal /p:CollectCoverage=truedotnet test src/GZCTF.Integration.Test/GZCTF.Integration.Test.csproj -v minimal /p:CollectCoverage=true(requires Docker; uses Testcontainers for K3s, MinIO, PostgreSQL)
- Testing framework:
- Unit tests: xUnit with
IRepository<T>and service mocking; examples inGZCTF.Test/UnitTests/. - Integration tests: Testcontainers for Docker/Kubernetes, MinIO S3, PostgreSQL, Redis; examples in
GZCTF.Integration.Test/Tests/. Covers dynamic container challenges, flag retrieval, storage operations, and repository data validation.
- Unit tests: xUnit with
- EF Core migrations (PostgreSQL):
dotnet ef migrations add <Name> --project src/GZCTF/GZCTF.csproj --startup-project src/GZCTF/GZCTF.csprojdotnet ef database update- OpenAPI (development only): JSON at
/openapi/v1.json, Scalar UI at/scalar/v1. - Generate TS API types for frontend (requires backend dev OpenAPI):
cd src/GZCTF/ClientApp && pnpm genapi
- Testing patterns: xUnit with
ConfigServiceTest.csandSignatureTest.csexamples; useMicrosoft.AspNetCore.Mvc.Testingfor integration tests.
- Database is required; set
ConnectionStrings:Database(env:GZCTF_ConnectionStrings__Database). App exits if missing. - Redis optional:
ConnectionStrings:RedisCacheenables distributed cache + SignalR backplane. - Storage:
ConnectionStrings:Storage(see prefixes above). Default is local disk under./files. - Telemetry: configure
Telemetryto enable Prometheus/OpenTelemetry/Azure Monitor;/metricsand/healthzare bound to port3000. - Forwarded headers: use
ForwardedOptions(proxies, networks) when behind reverse proxy.
- Port mapping types:
Default(random host ports) vsPlatformProxy(TCP-over-WebSocket). - Container providers: Docker (
DockerManager) and Kubernetes (KubernetesManager) withIContainerManagerabstraction. - Traffic capture: Optional recording of container network traffic to storage when
EnableTrafficCapture=true. - Challenge types: Static/Dynamic containers with environment variable flag injection.
- Resource management: Automatic cleanup and scaling based on team participation.
- CSP: Backend injects nonces at runtime for script/style integrity (
%nonce%placeholders inindex.html). - Authentication: Cookie-based with IdentityCore; role-based authorization via attributes.
- Rate limiting: Sliding window policies with Redis-backed distributed state.
- Input validation: Centralized model validation with localized error messages.
- File uploads: Hash-based storage with configurable retention and access control.
- Architecture & boundaries
- Routing: file-based via
vite-plugin-pagesscanningsrc/GZCTF/ClientApp/src/pages/**/*.tsx(seevite.config.mts). - UI/Styles: Mantine (
@mantine/*), CSS Modules (e.g.,src/.../styles/pages/About.module.css), theme customizable. - Data fetching:
axios+swr; typed API generated bypnpm genapifrom/openapi/v1.json. - Real-time: SignalR client
@microsoft/signalrtalks to/hub/{user|monitor|admin}; WSRX (@xdsec/wsrx) for TCP-over-WebSocket challenge entry. - i18n:
i18next+react-i18nextwith virtual manifest plugin (plugins/vite-i18n-virtual-manifest). - Enhancements: Shiki for code highlighting, ECharts for charts,
react-pdffor PDFs. - Key dirs:
src/pages(routes),src/components(shared UI likeWriteupSubmitModal.tsx,WsrxManager.tsx),src/styles,plugins/(custom Vite plugins).
- Routing: file-based via
- Common patterns
- Hooks:
useUser(),useUserRole(),useTeam()for state management;useTranslation()for i18n. - Components:
WithRolefor access control,ActionIconWithConfirmfor destructive actions,WithGameMonitorfor game-specific pages. - Notifications: Use
showNotification()from@mantine/notificationsfor user feedback. - Forms: Mantine forms with validation; use
useForm()hook for complex forms.
- Hooks:
- Dev server: Vite runs on
63000with proxy to backend (vite.config.mts); preview64000. - Proxy: forwards
/api,/swagger,/assets,/favicon.webp, and/hub(ws). - CSR security: Keep
%nonce%inindex.html; backend injects CSP nonce at runtime.