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¶
- The client requests a puzzle for the template.
- It brute-forces a nonce such that
sha256("<challenge>:<nonce>")starts withdifficultyleading zero bits. - 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.go — SolvePoW mirrors exactly what the server verifies.)