Markdown
Markdown extensions available in challenge descriptions, home page content, and sponsor descriptions.
rCTF supports Markdown in challenge descriptions, homepage content, and sponsor descriptions. marked parses it, rCTF adds alerts and a countdown timer, and DOMPurify sanitizes the resulting HTML.
The parser is in apps/web/src/lib/utils/markdown.ts, and apps/web/src/lib/components/markdown.svelte handles the interactive parts.
Alerts
rCTF supports GitHub-style blockquote alerts. The opening line is a blockquote containing [!TYPE], and any following blockquote lines form the alert body. The body is parsed as Markdown (except for CONNECTION, see below).
> [!NOTE]> The flag format is `rctf{...}`.
> [!WARNING]> Brute-forcing the endpoint will result in a rate limit.Six alert types are recognized. Anything else (e.g., > [!INFO]) falls back to a plain blockquote.
| Type | Trigger | Use for |
|---|---|---|
note |
> [!NOTE] |
General information. |
tip |
> [!TIP] |
Optional advice or shortcuts. |
important |
> [!IMPORTANT] |
Information players must read. |
warning |
> [!WARNING] |
Behavior that can cause problems if ignored. |
caution |
> [!CAUTION] |
Actions with potentially destructive consequences. |
connection |
> [!CONNECTION] |
Remote connection info for hosted challenges (see below). |
The trigger keyword is case-insensitive. Indentation and additional blockquote lines follow standard Markdown blockquote rules.
Warning (Visual reference only)
The docs site renders alerts with its own component, which looks different from rCTF’s runtime alert styling. The behavior described here is what challenge authors actually see in the rCTF frontend.
Connection callout
Use > [!CONNECTION] for the connection string or URL in a challenge’s Remote field.
> [!CONNECTION]> `nc challs.example.com 1337`
> [!CONNECTION]> https://web-chall.example.comUnlike the other alerts, the connection body is not parsed as Markdown. Wrapping backticks are stripped, and the content is rendered as either:
- A clickable link, if it starts with
http://orhttps://. - A monospaced code block, otherwise.
A copy button sits next to the value so players can paste it straight into a terminal.
Timer
The <timer /> element renders a live countdown for the competition. It must stand alone on its own line.
The CTF is live. Good luck!
<timer />The element takes no attributes. It always targets the global CTF schedule (startTime / endTime from the runtime client config). Behavior:
- Before the CTF starts, counts down to the start time with the label CTF starts in.
- After the start time, counts down to the end time with the label CTF ends in.
- After the end time, shows “The CTF is over.”
- If the event is archived, shows “The CTF is archived.”
The timer updates once per second on the client.
Note (No per-element target)
rCTF doesn’t support <timer to="..."> or <timer until="...">. The countdown target is always the global competition schedule. If you need per-challenge deadlines, spell them out in plain text inside the description.
Standard Markdown
The renderer supports CommonMark headings, lists, emphasis, links, images, code, and blockquotes, along with marked tables and autolinks. Task lists, footnotes, and strikethrough are not supported.
## Setup
Download the [source archive](./source.tar.gz) and run:
```bashdocker compose up```
The service listens on port `8080`.For the full list of what CommonMark supports, see the CommonMark spec.
Sanitization
DOMPurify sanitizes the parsed HTML before it reaches the page. This has the following effects.
<script>tags, inline event handlers (onclick=,onerror=, etc.), and dangerous protocols are stripped. Inline JavaScript will never execute.- Most HTML tags from DOMPurify’s default profile are allowed (e.g.,
<details>,<summary>,<sub>,<sup>,<kbd>). - The alert and timer extensions use
data-alert,data-type,data-content, anddata-timerto mark elements that need client-side behavior. Each element they create also receives a temporary, secretdata-nonce. A DOMPurify hook keeps the hydration attributes only when that nonce matches, then removes the nonce before returning the HTML. Markers written by hand have no effect because they do not carry the nonce and are stripped during sanitization.
If you need richer interactivity than these extensions cover, add it in the frontend code, not through embedded HTML in a description.