Goal
Make every lab instance use flags that are effectively unique and harder to predict, while keeping the learner-facing CTF{...} format.
Current observations
setup/flags.py uses one random secrets.token_hex(4) suffix for most challenge flags. That is 32 bits of randomness shared across challenges.
- Challenge 12 currently uses only the first 4 hex characters, which is 16 bits of randomness.
- Flag bases such as
hidden_files, file_search, and log_analysis reveal the challenge identity.
MASTER_SECRET is hard-coded in the repo and is used for completion-token signing through derive_verification_secret.
Proposed direction
Use a per-instance random secret and derive each challenge flag with HMAC-SHA256 or another standard keyed derivation. For example, derive each flag from instance_secret, challenge number, and purpose string, then encode a sufficiently long prefix into CTF{...}. This gives each challenge its own value and avoids a shared suffix pattern.
Questions to answer
- Should flags include challenge numbers for supportability, for example
CTF{01_<random>}, or should they contain no challenge hint at all?
- What entropy target should each flag have? A practical target could be at least 96 or 128 bits per flag.
- Should the completion-token signing secret be generated per lab instead of derived from a hard-coded repository constant?
- How should test tooling retrieve expected answers without reintroducing predictable flags?
Acceptance criteria
- Every non-example challenge flag has independent cryptographic randomness.
- No challenge uses a very short suffix such as 16 bits.
- Flag values no longer expose challenge names unless we intentionally keep a small challenge number prefix.
- Verify still stores only hashes for flag checking.
- Completion-token signing does not rely on a repository hard-coded production secret.
- The ctf-testing suite still solves flags from the VM artifacts, not from checked-in answer strings.
Links
Goal
Make every lab instance use flags that are effectively unique and harder to predict, while keeping the learner-facing
CTF{...}format.Current observations
setup/flags.pyuses one randomsecrets.token_hex(4)suffix for most challenge flags. That is 32 bits of randomness shared across challenges.hidden_files,file_search, andlog_analysisreveal the challenge identity.MASTER_SECRETis hard-coded in the repo and is used for completion-token signing throughderive_verification_secret.Proposed direction
Use a per-instance random secret and derive each challenge flag with HMAC-SHA256 or another standard keyed derivation. For example, derive each flag from
instance_secret, challenge number, and purpose string, then encode a sufficiently long prefix intoCTF{...}. This gives each challenge its own value and avoids a shared suffix pattern.Questions to answer
CTF{01_<random>}, or should they contain no challenge hint at all?Acceptance criteria
Links