Anti-cheat¶
CTFHive's anti-cheat system is human-in-the-loop: it detects suspicious submissions and raises CRITICAL events in the audit log. Admins review the evidence and decide whether to act. There is no automatic ban.
Layer 1: Cryptographic flag isolation¶
Every Principal (team or solo player) receives a unique flag for every challenge. The flag is derived with HMAC-SHA3-256:
Because each Principal has a different team_secret (32 random bytes,
generated once at Principal creation and never exposed), the flag for
Principal A is cryptographically distinct from the flag for Principal B —
even for the same challenge. A flag shared between two players cannot be
submitted by the second player for credit.
See Flag derivation for the full derivation spec and HMAC construction.
Layer 2: Submit-time detection¶
Implemented in ctfapp/services/anticheat.py, function
inspect_cheating_submission. It runs on every wrong submission.
The function performs two independent checks:
Check A — Flag sharing¶
The submitted flag is compared (constant-time) against the derived flag for every other active Principal for the same challenge. If it matches any of them, the submitter is presenting a flag that belongs to someone else.
Result: a FLAG_SHARE_DETECTED event at CRITICAL severity is written to the
audit log, including:
shared_from_principal_id— the Principal whose flag was stolensubmitting_principal_id— who submitted it
Check B — Flag replay¶
The submitted flag (hashed with the same algorithm used for Submission
storage) is compared against every other Principal's prior submission hashes
for the same challenge. If a hash match is found, the same flag has
already been submitted by a different player — meaning it was shared and
submitted multiple times.
Result: a FLAG_REPLAY_DETECTED event at CRITICAL severity is written,
including:
replayed_submission_id— the prior submission that matchedreplayed_from_principal_id— who submitted it beforesubmitting_principal_id— who is submitting it now
Both checks can fire on the same submission (the function accumulates all
findings into one dict). Each finding writes its own independent audit
event.
Layer 3: Submission rate limiting¶
Implemented in ctfapp/services/anticheat.py, function check_rate_limit.
This is enforced before inspect_cheating_submission runs.
| Parameter | Default | Config key |
|---|---|---|
| Wrong attempts before lockout | 3 | FLAG_WRONG_LIMIT |
| Lockout duration | 30 seconds | FLAG_LOCKOUT_SECONDS |
Rate limit state is stored in Redis using two keys per (principal_id,
challenge_id) pair:
wrong_count:<principal_id>:<challenge_id>— rolling wrong attempt counterlockout:<principal_id>:<challenge_id>— TTL key that blocks submissions while the lockout is active
When Redis is unavailable (development without Redis), the rate limiter
returns "allowed" to avoid blocking all submissions. Correct submissions call
clear_attempts to reset both keys.
How alerts surface to admins¶
Both FLAG_SHARE_DETECTED and FLAG_REPLAY_DETECTED are written at
CRITICAL severity. To review them:
- Navigate to
/admin/events. - Filter by Severity: CRITICAL or type
FLAGin the event type filter. - Each event's JSON payload contains the principal IDs involved.
Cross-reference with
/admin/usersto identify the players. - Decide whether to ban the user (
/admin/users/<id>/ban) or apply a score adjustment (/admin/dashboard, manual score form).
There is no auto-ban. An organizer must review and act.
Roadmap (not yet implemented)¶
Roadmap — not yet implemented
The following layers are planned but not present in the codebase. Do not expect them to function.
VPN / packet telemetry correlation — The WireGuard gateway has packet
capture plumbing (ctfapp/lab/packet_capture.py,
ctfapp/models/packet_capture.py) and the dashboard shows VPN peer
status. However, this data does not currently feed any anti-cheat scoring.
The planned feature would flag submissions from Principals that sent zero
packets to the relevant challenge host.
Solve-time z-score — Flag a submission where the time between first challenge view and first correct solve is implausibly short relative to the distribution of all other solvers. The streaming mean/stddev logic and the 5th-percentile threshold are not yet implemented.
Session fingerprint pinning — A sticky (JA4_hash, user_agent_hash,
canvas_hash, timezone) tuple compared across sessions. There is no
auth/fingerprint.py module and the JA4 or canvas fingerprinting
code does not exist.
Cross-tenant correlation — Detecting the same browser fingerprint playing in two simultaneous events under different handles. This requires the CTFHive control plane, which is a separate service not yet deployed.
Summary¶
| Layer | Status | What it catches |
|---|---|---|
| Cryptographic flag isolation | Implemented | Flags from team A cannot be valid for team B |
| Submit-time flag sharing | Implemented | Wrong submission matches another Principal's derived flag |
| Submit-time flag replay | Implemented | Wrong submission hash matches a prior submission by a different Principal |
| Submission rate limiting | Implemented | Brute-force guessing — 3 wrong attempts triggers a 30-second lockout |
| VPN packet correlation | Roadmap | Submissions from Principals with no recorded network activity |
| Solve-time z-score | Roadmap | Implausibly fast solves relative to the peer distribution |
| Session fingerprint pinning | Roadmap | Same session diverges mid-competition |
| Cross-tenant correlation | Roadmap | Same player across multiple simultaneous events |