Skip to content

Proof of work

Spawning an instance costs the cluster real resources. To stop scripted spam (or a hostile player spawning hundreds of pods), a template can require solving a small computational puzzle before each launch.

Enable it per template:

spec:
  pow:
    enabled: true
    difficulty: 20   # leading zero bits required
    algorithm: sha256
    ttl: 5m          # puzzle validity window

How it works

  1. The client requests a puzzle for the template.
  2. It brute-forces a nonce such that sha256("<challenge>:<nonce>") starts with difficulty leading zero bits.
  3. It sends the solved puzzle along with the create-instance request. Puzzles are single-use and expire after ttl.

Difficulty 20 requires ~1M hashes on average — around a second of client CPU, negligible for a human, expensive at spam scale. Each +1 doubles the work.

Client flow

# 1. Get a puzzle
curl -s -H "Authorization: Bearer $KEY" \
  "localhost:8080/api/v1/pow/challenge?template=web-sqli-101"
# {"challenge": "a1b2c3...", "difficulty": 20, "expiresAt": "..."}

# 2. Solve it (find nonce where sha256("<challenge>:<nonce>") has 20 leading zero bits)

# 3. Create the instance with the solution
curl -s -X POST -H "Authorization: Bearer $KEY" localhost:8080/api/v1/instances -d '{
  "template": "web-sqli-101",
  "team": "team-alpha",
  "powChallenge": "a1b2c3...",
  "powNonce": 1048113
}'

A missing or invalid solution gets 428 Precondition Required.

Solver example

import hashlib, itertools

def solve(challenge: str, difficulty: int) -> int:
    for nonce in itertools.count():
        digest = hashlib.sha256(f"{challenge}:{nonce}".encode()).digest()
        bits = int.from_bytes(digest, "big")
        if bits >> (256 - difficulty) == 0:
            return nonce

(The Go reference implementation lives in internal/api/pow.goSolvePoW mirrors exactly what the server verifies.)