Files
HIPCTF2/docs/database/challenges.md
T

6.4 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-22T16:44:54Z

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. Declared in the initial InitSchema1700000000000 migration (defaulting to strftime('%Y-%m-%dT%H:%M:%fZ','now')); the AddCategoryTimestampsAndUniqueAbbrev1700000000200 migration adds it as a no-op for older pre-existing databases.
updated_at TEXT ISO 8601 timestamp the row was last updated (bumped on PUT /api/v1/admin/categories/:id). Same provenance as created_at.

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). A forward-only repair migration, RepairCategorySchemaAndSystemCategories1700000000400, runs after that one on existing databases that pre-date the canonical six keys (it adds created_at/updated_at if missing, deletes obsolete FOR/OSI system rows, rewrites name/abbreviation/description/icon metadata on canonical rows, inserts any missing canonical row, and guarantees unique indexes on system_key and abbreviation). 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 (re-asserted by 1700000000400).

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