Skip to content

Team mode vs Solo mode

CTFHive uses a Principal as the single scoring identity. Whether you run a team event or a solo event, the scoreboard always ranks Principals — which means the challenge submission, flag derivation, and score ledger code is identical for both modes.


The Principal abstraction

A Principal is a database record that owns all scoring activity for one competitor unit:

Field Description
public_id UUID shown on scoreboards — safe to expose
kind "team" or "solo"
team_secret 32 random bytes, encrypted at rest — the HMAC key for flag derivation
team_id Set when kind = "team", NULL otherwise
user_id Set when kind = "solo", NULL otherwise
score_total Denormalized running total for fast ORDER BY
last_solve_at Timestamp of last correct submission

The team_secret is generated once at Principal creation and never changes. It is never exposed in any API response or template. Flags are always re-derivable from (ADMIN_KEY, team_secret, challenge_id) — see Flag derivation.

How it maps to users

Team mode
─────────────────────────────────────────────────────────
  User A (captain)  ──┐
  User B (member)   ──┤  Team "0xDeadBeef"  →  Principal (kind=team)
  User C (member)   ──┘       team_id=7            score_total
                                                    team_secret

Solo mode
─────────────────────────────────────────────────────────
  User D  →  Principal (kind=solo)
                user_id=9
                score_total
                team_secret

Every solve, submission, and score event references the Principal — not the user or team directly. This means the scoring path is the same code regardless of mode.


Choosing a mode

Team events

Use team mode when players should collaborate. Each team gets one flag set, one set of container instances, and one position on the scoreboard.

Best for: university CTFs, company hackathons, large community events.

Solo events

Use solo mode when every player should score independently. Each user gets their own Principal with its own unique flag set.

Best for: qualifiers, individual challenges, learning scenarios.

Mixed events

Both modes can coexist in one event. A player registers, selects their preference, and can switch later (subject to admin settings — see Site settings).


Player flow: team mode

1. Registration

When registration is open, a player chooses Team on the registration form. After logging in they are directed to /team/.

2. Create a team (captain)

The captain fills in a team name (2–80 characters, must be unique). Optionally they set a team password (at least 4 characters). On success:

  • A Team row is created.
  • The creating user is added as a TeamMember with role = "captain".
  • A Principal(kind="team") is created and linked to the team.
  • Flags are pre-generated for all currently visible challenges.
  • The join token (16-character URL-safe string) is displayed.

3. Join a team (member)

Members paste the join token into the Join form. If the team has a password they must supply it too. On success:

  • A TeamMember(role="member") is created.
  • If the user had a solo Principal, it is deactivated automatically.
  • The user now scores through the team's Principal.

4. Team management (/team/manage)

The manage page shows:

  • Team name, join token, member list with roles.
  • Active WireGuard VPN keys (for lab challenges) grouped by challenge.
  • Captain-only controls: set or remove the team password.

Captain constraints

A captain cannot leave their own team. They must disband the team or transfer captaincy before leaving. The UI enforces this: the leave button is hidden for captains.

5. Leave a team (member only)

Regular members can leave via POST to /team/leave. Leaving marks the TeamMember as inactive. The player can then join a different team or switch to solo mode.


Player flow: solo mode

A player who did not join a team, or who left a team, can activate solo mode from /team/. This creates Principal(kind="solo", user_id=<id>) and pre-generates flags.

Solo and team Principals are mutually exclusive

When a user joins or creates a team, their solo Principal is deactivated. When a user switches to solo, they must leave their team first.


Site settings

These AppSetting keys control team and solo behaviour. Admins change them at /admin/settings:

Key Default Effect
registration_open true Whether new users can register
allow_solo_play true Whether solo Principals can be created
allow_team_switch true Whether the solo-switch UI is shown
max_team_size 4 Maximum members per team (enforced at join time)

When allow_solo_play is false, the "Switch to solo" button is hidden and POST to /team/switch-to-solo returns an error flash.

When allow_team_switch is false, the solo-to-team option is hidden on the team landing page.


Audit events written by the team system

Every meaningful state change writes a tamper-evident event to the audit log (see Audit chain):

Event type Trigger
TEAM_CREATED Captain creates a team
TEAM_JOINED Member joins a team
TEAM_LEFT Member leaves a team
TEAM_PASSWORD_CHANGED Captain sets or removes team password
SOLO_MODE_ACTIVATED Player switches to solo mode