Files
HIPCTF2/docs/database/challenges.md
T

5.9 KiB
Raw Blame History

type, title, description, tags, timestamp
type title description tags timestamp
database Challenge Tables category, challenge, challenge_file, and solve tables — how CTF challenges and scoring are stored.
database
challenge
category
solve
2026-07-21T14:18:00Z

Tables

category

Column Type Description
id TEXT PK UUID v4.
system_key TEXT Non-null for system categories (e.g. CRY, MSC, PWN, REV, WEB, HW); unique where NOT NULL; NULL for user-created categories.
name TEXT Display name (e.g. Cryptography).
abbreviation TEXT Short label (e.g. CRY), 26 chars, server-uppercased. Globally unique — enforced by uq_category_abbreviation.
description TEXT Optional description.
icon_path TEXT Default icon URL (e.g. /uploads/icons/CRY.png).
created_at TEXT ISO 8601 timestamp the row was created (added by AddCategoryTimestampsAndUniqueAbbrev1700000000200).
updated_at TEXT ISO 8601 timestamp the row was last updated (added by the same migration; bumped on PUT /api/v1/admin/categories/:id).

System rows are seeded by the UpdateSystemCategoryKeys1700000000300 migration so the canonical keys are CRY (Cryptography), MSC (Misc), PWN (Binary exploitation), REV (Reverse Engineering), WEB (Web), and HW (Hardware). The migration drops any legacy seeded rows that no longer match the canonical set before inserting the missing entries. User-created categories leave system_key as NULL.

The uniqueness on abbreviation is enforced both by application validation (uppercased on save, duplicates rejected) and by the uq_category_abbreviation index created in migration 1700000000200.

challenge

Column Type Description
id TEXT PK UUID v4.
name TEXT Display name.
description_md TEXT Markdown description.
category_id TEXT FK → category.id.
difficulty TEXT 'low' / 'med' / 'high'.
initial_points INTEGER Starting point value.
minimum_points INTEGER Floor after decay.
decay_solves INTEGER Number of solves at which decay reaches the minimum.
flag TEXT Submission string expected from players.
protocol TEXT 'nc' (netcat-style connection) or 'web' (browser-based).
port INTEGER TCP port when relevant (nullable).
ip_address TEXT IP the challenge listens on (defaulted from setting.defaultChallengeIp).
created_at TEXT ISO 8601 timestamp.

challenge_file

Column Type Description
id TEXT PK UUID v4.
challenge_id TEXT FK → challenge.id.
original_filename TEXT Filename from upload.
stored_path TEXT Path on disk under UPLOAD_DIR/challenges/.

solve

Column Type Description
id TEXT PK UUID v4.
challenge_id TEXT FK → challenge.id.
user_id TEXT FK → user.id.
solved_at TEXT ISO 8601 timestamp the solve was recorded.
points_awarded INTEGER Points actually awarded (after decay).
base_points INTEGER Snapshot of challenge.initial_points at solve time.
rank_bonus INTEGER Extra points from rank (e.g. first-blood).

Uniqueness is enforced by uq_solve_challenge_user on (challenge_id, user_id) — a user can solve a given challenge only once.

Scoring formula

points_awarded = clamp(initial_points - decay, minimum_points, initial_points) where decay = max(0, base_points - minimum_points) * solvesBefore / decay_solves, and additional rank_bonus may be added for early solves.

(The exact scoring algorithm lives in the future solve-recording service and is consumed by SseHubService whenever a new solve is published.)

See also