Skip to content

Security ​

Goal: competitive results come only from legal, real-time gameplay of a registered ruleset. Not a goal, and not claimed: proving a human played, or that a run came from an official build.

Controls ​

ThreatControlTest
Fabricated score or factsThe server computes score and facts from inputs; client claims are ignored.integrity.test.ts
Modified client simulation, altered seed or rulesState checksum per chunk must match the verifier.integrity.test.ts
Illegal actionsInput bits bounded by the ruleset's button count; rules enforced by the simulation.integrity.test.ts
Speed hacksInput may lead real time by at most 6 ticks + 0.1%, capped at 60 ticks.integrity.test.ts
Slow motion, late upload, rewritten historyInput must arrive within 1.5 s; sequence and tick continuity; an accepted seq is immutable.timing.test.ts, integrity.test.ts
Pause abuseServer-approved pauses only, at the flushed tick; 15 min each, 60 min total, 24 h session. Resume removes only the paused time, so pause cycles cannot launder slow motion or bank lead.timing.test.ts
Duplicate rewardsEvent id per run; one storage transaction for rewards and outbox; personal bests only increase.recovery.test.ts, achievements.test.ts
Another player's runEach run lives in its owner's object; the token secret is checked there.integrity.test.ts
Malicious Lua or bytecodeVerifier code is packed by the administrator and deployed. Binary chunks are refused; I/O, loaders and debug are absent; instruction and memory caps.vm.test.ts
Request floodsBody limits before parsing; per-address throttle on every route (IPv6 by /64); guest throttle; run throttle and daily admission spent only after the credential check, inside the player's object; unknown players' objects write no storage.api.test.ts, integrity.test.ts
Verifier faultsLua errors, Wasm traps and corrupt checkpoints reject only that run (verifier_error); alarms isolate runs from each other.recovery.test.ts
Moderation gapsSuppressed runs and disabled rulesets are checked again when a result publishes.recovery.test.ts

Credentials ​

  • A guest token is <player_id>.<secret>, 256 random bits of secret. Only its SHA-256 is stored, in the player's object. The token is returned once. It is a bearer credential: anything that reads the game's save can use it.
  • ADMIN_TOKEN is a Worker secret of at least 32 characters, compared as hashes. Never ship it or any signing key in a game build.
  • Native clients require HTTPS and initialized certificate verification. The reference games use the vendored public issuer roots through scoreosis_tls, which works around Defold 1.13.1's PEM loader defect. Missing roots, a missing extension, or failed initialization stops requests before credentials are sent. Only debug builds may use plain HTTP on loopback for development; the client refuses other insecure URLs before sending credentials.

Known limits ​

  • Bots and assistance that produce legal inputs in real time pass verification. The run seed is returned at start, so a bot can simulate ahead. Moderation (hide, suppress) is the answer; never claim human or cheat-proof verification.
  • Players can think during pauses.
  • Build ids and platform labels are claims. Platform boards are labels, not attestation.
  • Country is Cloudflare's view of the request at run start; a VPN changes it.
  • Guest creation cannot stop one person making many guests; throttles slow it.
  • Cloudflare's rate-limit binding is per location and eventually consistent. Exact invariants (one active run, sequences) live in the player's object.
  • A free plan can be exhausted by abuse. One network address may start at most MAX_RUNS_PER_ADDRESS ranked runs a day, however many guests it creates, so exhausting the daily allowance takes many addresses. An attacker with many IPv4 addresses or IPv6 /64s can still do it. The counter stores an HMAC of the address under ADDRESS_KEY, kept one day; anyone holding both the key and the table can still recover IPv4 addresses by brute force. Capacity errors stop new ranked runs; local play continues.
  • itch.io serves every HTML5 game from the shared html-classic.itch.zone origin, so other games there can read browser storage, including the guest token. Treat web guest tokens as low-value; anything more needs per-game hosting.

Logging ​

Structured events only (src/log.ts). Never log tokens, authorization headers or IP addresses.

Revoking achievements ​

Achievement state lives in each player's object (bt_achievements, bt_scores, bt_events). To revoke after a compromised ruleset: disable the ruleset (POST /v1/admin/games/:game/rulesets/:ruleset/disable), then for each affected player run a one-off script with wrangler against the object that deletes the run's bt_events row and recomputes bt_scores and bt_achievements by replaying the remaining events through a fresh engine. No endpoint does this automatically; it rewrites player progress and needs review per incident.

MIT licensed