Skip to content

First-class Attack-Defense & King-of-the-Hill competition modes #267

Description

@tbcsec

Filed as a single feature / tracking issue (owner decision — not an epic with
child issues). The phased breakdown below is tracked as sub-tasks within this issue.

Depends on #266 (on-demand per-team challenge instances): a hard dependency for
Attack-Defense per-team services, a soft dependency for the KotH "hold" variant. KotH
best-solution ranking ships early with little infrastructure.

Scope note: this is a multi-quarter effort. KotH lands value first; Attack-Defense
is substantially larger and lands later.

Summary

Flagpost, like CTFd and rCTF core, runs only jeopardy. This adds two further
competition modes as first-class citizens:

  • King-of-the-Hill (KotH): round/tick-based; each round ranks submissions or holders
    by an objective and awards points to the leader(s); supports both a "best-solution
    ranking" variant and a "hold the objective and accrue" variant.
  • Attack-Defense (A/D): tick engine; per-team defended service instances; per-tick
    flag rotation and injection; service SLA/uptime checks; three scoring components
    (attack, defense, SLA); stolen-flag submission with dedup and self-flag rejection.

Both reuse Flagpost's real-time board, presence, spectator/venue mode, automation engine,
and RBAC.

Motivation & competitive context

Jeopardy is increasingly commoditised by AI: at BSidesSF 2026 autonomous agents cleared
an entire challenge set within minutes of release, and agent frameworks post near-total
solve rates on standard benchmarks. A/D and KotH are far more discriminating and remain
unserved by modern self-hosted platforms — DEF CON-tier events build bespoke tick
engines and infrastructure, and the only turnkey historical option (FBCTF) is legacy.
Being the modern, self-hostable platform that runs these natively is a
category-defining position, and it is the natural payoff of the instancing feature.

Goals

  1. competition.mode as a first-class attribute: jeopardy (existing),
    king_of_the_hill, attack_defense.
  2. A pluggable scoring engine per mode (generalise the existing static-vs-dynamic
    logic into a ScoringEngine strategy).
  3. An append-only score-event log as the source of truth for all point changes
    (flag submissions, decay recomputes, tick awards) — audit trail + recompute basis.
    (Directly inspired by rCTF v2's score-events log.)
  4. A tick/round engine: configurable tick length, start/stop, honours existing
    competition pause; emits round lifecycle events.
  5. KotH: per-challenge objective with a ranking function; per-round leader points;
    optional "hold/accrue" with overtaking.
  6. A/D: long-lived, per-team, patchable service instances; organiser-supplied
    checkers (flag placement + SLA/functionality) run each tick in a sandbox; flag
    validity window; stolen-flag submission; three-component scoring; configurable
    scoreboard delay.
  7. Mode-aware scoreboard & spectator views reusing venue mode (A/D and KotH are
    excellent to broadcast).

Non-goals (initial)

  • Building a network fabric/VPN between competitor machines. Provide a documented
    integration model and hooks
    (endpoint registry) rather than an SDN in v1.
  • Automated exploit detection / traffic capture-replay.
  • Arbitrary organiser code execution beyond sandboxed checkers.

Proposed design

Mode & scoring abstraction

Add competition.mode. Introduce a ScoringEngine strategy resolved per mode, with
jeopardy re-implemented against it (regression guard). Propose an ADR for the mode
abstraction and the score-event log.

Score-event log

Append-only score_event table: subject_id, source (submission | decay | tick |
capture | sla | adjustment), delta, competition_id, round_id?, metadata, ts.
Scoreboard reads become a projection/aggregate. This also cleans up dynamic-scoring
recomputes for jeopardy.

Tick/round engine

Scheduler with configurable tick length and window; persists a round row per tick;
emits round.started / round.ended on the event bus; respects competition pause.
Same concurrency caveat as instancing (single-process default) — run off the request
path; the worker-process question is shared with #266.

KotH design

  • Per-challenge objective + ranking function (e.g. best submitted metric, or
    current holder token).
  • Best-solution variant: each round, submissions are scored/ranked; top team(s) earn
    round points; cumulative via the score-event log. No instancing required if the
    objective is a submitted artifact/flag/metric
    — this is the low-infra, AI-resistant
    early slice.
  • Hold/accrue variant: a holder token transfers on overtaking; holding across ticks
    accrues points. The "hold a shared box" flavour depends on instancing + networking.
  • Optional later: DEF CON-style colour/retire mechanics.

Attack-Defense design

  • Per-team service instances via the On-demand, per-team containerised challenge instances #266 provisioner, but with a distinct
    lifecycle
    : long-lived for the event and team-writable/patchable (teams harden
    their own copy), unlike ephemeral jeopardy instances. Call this out explicitly in the
    provisioner work.
  • Checkers (SLA + flag placement): organiser-supplied scripts per service, run each
    tick to (a) place a fresh flag and (b) verify functionality. Run sandboxed — this
    is a major attack surface (organiser code, every tick, every team).
  • Flag rotation & validity: flags valid for N ticks; a submission endpoint accepts
    stolen flags; reject own-team flags; dedup so each flag scores once per attacker.
  • Scoring components: attack (captures), defense (not being captured / uptime), SLA
    (service up + functional). Configurable weights; configurable scoreboard delay
    (default N ticks, per DEF CON convention).
  • Networking: teams need routed access to opponents' services. Provide a documented
    model
    — platform exposes each team's service at a stable address via an endpoint
    registry
    ; recommend WireGuard/overlay or per-team ingress as the fabric. Treat the
    fabric as deployment-provided with platform hooks in v1 (open question).

Frontend

  • KotH: round leaderboard, hold timer, round countdown.
  • A/D: team service-status panel (your services up/down), live capture feed,
    per-component score breakdown, tick timeline.
  • Extend spectator/venue mode for both (broadcast-friendly).

Automation & events

New events: round.started, round.ended, flag.captured, service.down,
service.recovered, koth.lead_changed. Actions can announce first capture, alert on
service-down, etc. (add to the §3.2 catalogue).

RBAC

Permissions for checker management, tick control (start/stop/advance), and viewing raw
captures. Add to the catalogue with default role grants.

Import/export

Extend the challenge schema for A/D service specs and checker references so mode-specific
challenges remain portable.

Phasing

  • Phase 1 — KotH (best-solution ranking): mode abstraction + score-event log + tick
    engine + ranked-per-round scoring. Ships early, AI-resistant, minimal infra.
  • Phase 2 — KotH (hold/accrue): holder tokens, overtaking, accrual; shared-box
    flavour once instancing Phase 3 lands.
  • Phase 3 — A/D MVP: long-lived patchable per-team instances; sandboxed checkers;
    flag rotation + capture submission; basic three-component scoring.
  • Phase 4 — A/D hardening: SLA functionality checks, scoreboard delay, network-fabric
    guidance/hooks, retire/colour mechanics, load hardening.

Proposed breakdown (sub-tasks tracked within this issue)

  • ADR: competition mode abstraction + score-event log (jeopardy re-expressed via ScoringEngine)
  • competition.mode attribute + mode plumbing (jeopardy default unchanged; surfaced in settings)
  • Append-only score_event log + scoreboard projection (all jeopardy point changes flow through it; dynamic recompute preserved)
  • Tick/round engine + round model + lifecycle events (honours pause; off the request path)
  • KotH scoring engine — best-solution ranking (per-round ranking → leader points → score-event log)
  • KotH frontend — round leaderboard + timers
  • KotH hold/accrue variant + overtaking (holder token; accrual across ticks)
  • A/D: long-lived patchable per-team service instances (distinct lifecycle from ephemeral On-demand, per-team containerised challenge instances #266 instances) — depends on On-demand, per-team containerised challenge instances #266
  • A/D: checker runtime (flag placement + SLA) — sandboxed, resource/time-limited, failures isolated
  • A/D: flag rotation, validity window, stolen-flag submission + dedup (own-flag rejection; per-attacker dedup)
  • A/D: three-component scoring (attack / defense / SLA) + scoreboard delay (all via score-event log)
  • A/D frontend — service status, capture feed, component breakdown
  • Mode-aware events + automation actions
  • RBAC: checker management, tick control, raw-capture view
  • Spectator/venue mode for A/D & KotH
  • Networking integration model + endpoint registry (A/D) — documented fabric + platform hooks (needs its own ADR)
  • Import/export for mode-specific challenge specs
  • Docs: running a KotH event / running an A/D event

Overall acceptance criteria

  • An organiser can create a competition in king_of_the_hill mode, run ticked rounds,
    and see leader points accrue correctly through the score-event log.
  • An organiser can create an attack_defense competition where each team receives a
    patchable service, checkers place flags and measure SLA each tick, captures score, and
    the delayed scoreboard reflects attack/defense/SLA components.
  • Jeopardy behaviour is unchanged and regression-tested after the scoring refactor.
  • Both modes broadcast via spectator/venue mode.

Risks & open questions

  • Network fabric ownership: how much does the platform own vs. document? (ADR)
  • Checker sandbox security: running organiser code every tick per team is a large
    attack surface — isolation and resource limits are critical.
  • Instance lifecycle mismatch: long-lived patchable A/D instances differ from the
    ephemeral model in On-demand, per-team containerised challenge instances #266 — reconcile early.
  • Scoring fairness/anti-abuse and scoreboard consistency under delay.
  • Load: checkers × teams × ticks can be heavy; capacity-plan and document.

Derived from an internal feature brief. This is the canonical tracking issue for the feature.

Metadata

Metadata

Assignees

No one assigned

    Projects

    Status
    Backlog

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions