-
-
Notifications
You must be signed in to change notification settings - Fork 19
Authoring a Lab
This guide is for content writers. It covers the voice, the structure, and the rules a lab needs to merge. If you have not read Lab Anatomy and Flag Format, start there.
- Copy
labs/_template/intolabs/<track>/<lab-name>/. - Fill in the metadata.
- Write the brief.
- Build the service.
- Run the validator.
- Open a pull request.
CI runs the same validator. All checks must pass before review.
Written content follows one voice. Short declaratives. One idea per sentence. Sentence-case headings. No exclamation marks, no em-dashes, no superlatives, no adverbs, no hype, no larp.
Terms that mean specific things:
| Term | Meaning |
|---|---|
lab |
one service, one brief, one flag |
flag |
the duck{...} value |
solve |
the act and the result of finding the flag |
writeup |
a player's published explanation of a solve |
Difficulty, lowercase in prose, uppercase only in mono badges:
easy, medium, hard, insane.
Put values in code spans: ports, commands, formats. This keeps prose clean and makes the concrete things scannable.
| Avoid | Prefer |
|---|---|
| "This lab will teach you a lot" | "Find the flag in the restricted report." |
| "Never underestimate the power of IDOR" | "The portal checks ownership on one route." |
| "Wow, what a challenge!" | "Wardens file crossing reports. One is restricted." |
The brief points at the surface. It does not hand over the solve.
lab.yml is flat YAML. Every field required.
| Key | Rules |
|---|---|
name |
matches the directory name, lowercase, hyphens |
track |
one of web, binary, crypto, network, osint
|
difficulty |
one of easy, medium, hard, insane
|
description |
one line, shown in the lab index |
flag_hash |
SHA-256 of the full flag string, 64 lowercase hex |
README.md has three parts.
-
Title: the lab name,
EASY·web. - Brief: the story and the surface. Set up the world in two or three sentences. Name who is involved and what is at stake.
- Setup and Goal: exact commands to run, the URL to open, and how to check the solve.
Write the goal as an instruction, not a hint. "Find the restricted report and its flag" states the target without giving the path.
Grade against what a player does, not how long it takes. See Tracks and Difficulty for the rubric. If a lab sits between two tiers, grade down.
-
Format
duck{...}, matching^duck\{[a-z0-9_]{16,40}\}$. -
Generate the body randomly. One flag per lab, never reused.
-
The plaintext appears only in lab internals, the service files under the lab directory. Never in
lab.yml, never in the brief, never outsidelabs/. -
Compute the hash from the exact flag string, braces included:
printf '%s' 'duck{your_flag_here}' | sha256sum
- The lab runs offline once images are pulled.
- No runtime calls to external services, no license servers, no phone-home.
- Install dependencies at build time only.
Pin base images to a full version tag. python:3.12-alpine is acceptable,
python:latest is not. Prefer a digest pin when the base publishes one.
Expose one documented host port per lab. State it in the brief and in
docker-compose.yml. 8080 is the default. Use alternatives only when the
lab needs them.
- No real personal data.
- No third-party copyrighted content without permission.
- A vulnerable service is the point. It is not a reportable weakness.
python3 scripts/validate.py
python3 scripts/validate.py --composeBoth must pass from a clean clone. Then open the pull request and meet every item on the Reviewer Checklist.
openlabs is an open-source project. Code is Apache-2.0. Written content is CC-BY-4.0.