HuntCode
In-browser code editor
The editor and execution environment behind HunterFr's challenges. Writing code in a browser tab is the trivial part; running a stranger's code without handing them the host is the actual project.
- Period
- 2026 →
- Role
- Architecture, development, sandboxing
- Stack
- TypeScript · Next.js · Node.js · Docker · Linux · WebSockets
Overview
HuntCode started because sending players to an external editor broke the one rule HunterFr has: the platform should never be the obstacle. Copy-pasting between a tab and a local toolchain is friction, and friction is where beginners stop.
So the editor moved into the page. That part took a week. The remaining time has gone entirely into the half that matters: arbitrary code, written by people who are on the site specifically to find holes, has to execute somewhere.
Objectives
- 01
Hostile by default
The threat model is not 'a user might make a mistake'. It is 'this user is trying to leave the container, and they read the same papers I do'.
- 02
Disposable execution
Nothing survives a run. Every execution gets a fresh filesystem and is destroyed after it, so persistence is impossible rather than merely unsupported.
- 03
Fast enough to feel local
If a run takes three seconds to start, people stop iterating and start guessing. Container startup budget is part of the UX spec.
- 04
Deny egress, always
No outbound network from an execution container. It removes exfiltration, removes the platform as a proxy, and removes an entire category of abuse.
Architecture
| Layer | Choice | Why |
|---|---|---|
| Editor | Browser editor component, server-authoritative state | The client is a view. What ran, what it produced and what it scored are decided server-side, because the client is under the player's control. |
| Transport | WebSocket for output streaming | Output arrives as it is produced instead of after the process exits, so a long-running or hanging program is legible rather than a spinner. |
| Runner | One short-lived container per execution | Read-only root filesystem, no capabilities, non-root user, memory and CPU ceilings, pid limit, wall-clock timeout, no network namespace access outward. |
| Scheduling | Queue with per-user concurrency limits | The cheapest attack on a runner is not an escape, it is asking for ten thousand of them. Concurrency is capped per account before it is capped globally. |
Challenges
- Problem
Cold container start is slow enough to be felt on every single run.
SolutionA small pool of pre-warmed containers is kept ready and handed out on demand, then destroyed rather than reused. Start cost moves off the critical path without any state crossing between players.
- Problem
A program that never terminates is indistinguishable from one that is merely slow.
SolutionWall-clock timeout, output byte ceiling and pid limit are enforced by the runtime, not by the program. Exceeding any of them ends the run with a specific reason the player can read.
- Problem
Streaming output straight to the page is an injection sink.
SolutionOutput is treated as bytes, never as markup, and is rendered into a text surface. The terminal is a rendering of untrusted data, so it is built like one.
Roadmap
- Mid 2026Editor in-page, server-authoritative runsShipped
- Mid 2026Streamed output over WebSocketShipped
- 2026Sandbox hardening and escape testingIn progress
- 2026Additional language runtimesPlanned
- 2026Public rollout across all HunterFr challengesPlanned
What it taught me
Every performance shortcut in a sandbox is a reuse of something, and reuse between players is the whole risk. Pre-warming is fine; recycling is not.
Limits enforced by the thing being limited are not limits.
Writing the attacker's side of your own feature is the fastest way to find out which of your assumptions were decorative.
HunterFr
