Skip to content

Add CI, wire-protocol fuzz suite, and prototype-key hardening - #2

Merged
techmore merged 2 commits into
mainfrom
improve/ci-and-wire-hardening
Oct 21, 2026
Merged

techmore merged 2 commits into
mainfrom
improve/ci-and-wire-hardening

Conversation

@techmore

Copy link
Copy Markdown
Owner

Summary

The repo had no CI and no test that exercises the WebSocket protocol with hostile input. This adds both, and fixes the two bugs the new fuzz suite surfaced.

What changed

  • CI (.github/workflows/ci.yml): Node 22 and 24 matrix; node --check over every shipped module (server, data, client JS, scripts, tests — there is no bundler, so a parse error is a runtime error), then npm test.
  • Fuzz suite (test/wire-fuzz.test.mjs): every wire message type × every field × junk values (wrong types, prototype keys, 100 KB strings, NUL, lone surrogates) in login / charselect / chargen / playing states, plus all 183 registered verbs × junk arguments. Contract: route() never throws and never emits a SESSION_ERROR.
  • Fix: prototype-key crash in the command dispatcher. Typing __proto__ as a verb resolved through p.aliases[first] / DIR_ALIASES[cmd] and threw cmd.replace is not a function → "Something went wrong" to the player and a stack trace in the server log. Alias and direction lookups now use own-key checks (server/commands/index.js, world.js, player.js).
  • Fix: table lookups keyed by raw client input. raceById / guildById / itemById / creatureById / skillById returned Object.prototype members for ids like constructor; charcreate with race constructor leaked a TypeError message instead of "Unknown race". All five now require an own string key (data/*.js, server/commands/join.js).
  • README: CI badge and a note on what CI runs.

How verified

  • npm test → 959 tests, 956 pass. The 2 failures are test/puffer-circle.test.mjs ("seed 42 scripted rotation earns Circle 2 within 512 activities") hitting its 30 s timeout under CPU load; it fails identically on unmodified main on the same machine and passes when the box is idle — pre-existing flake, not touched here.
  • node --test test/wire-fuzz.test.mjs → 7/7 pass (fails on main with the cmd.replace TypeError).

Risks / follow-ups

  • The *ById helpers now return null for non-string ids. All callers pass strings today (full suite green), but any future caller passing a number will get null rather than a hit.
  • Follow-up: the puffer-circle subprocess test deserves a longer timeout or a smaller activity budget so it is stable on loaded CI runners.
  • No linter is configured; adding ESLint is a separate, noisier change.

🤖 Generated with Claude Code

techmore and others added 2 commits October 20, 2026 06:19
The repo had no CI. Add a GitHub Actions workflow (Node 22 + 24) that
syntax-checks every shipped module with `node --check` and runs `npm test`.

A new wire-level fuzz suite (test/wire-fuzz.test.mjs) feeds every client
message type, every field, and every registered verb junk values in every
session state, asserting that nothing throws or surfaces as a SESSION_ERROR.
It found one real crash and one message leak:

- Typing `__proto__` (or any Object.prototype key) as a verb resolved
  through `p.aliases[first]` / `DIR_ALIASES[cmd]` and threw
  `cmd.replace is not a function`, which reached the player as
  "Something went wrong" and a server-log stack trace. Lookups now use
  own-key checks.
- `raceById` / `guildById` / `itemById` / `creatureById` / `skillById`
  returned Object.prototype members for `constructor`-style ids, so a
  `charcreate` with race `constructor` leaked a TypeError message instead
  of "Unknown race". The lookups now require an own string key.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Two seed-42 Circle 2 runs take ~29s on a GitHub-hosted Node 22 runner, so
the 30s outer timeout SIGKILLed the engine mid-run ("Engine exited before
response" + EPIPE). The test also fails this way on main when run on that
runner; it was only exposed now because this PR adds CI. Per-request 8s
timeouts are unchanged, so a hung engine still fails fast.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@techmore

Copy link
Copy Markdown
Owner Author

Automated PR audit (2026-10-21): Test (Node 22) failed because test/puffer-circle.test.mjs (a pre-existing suite this PR does not touch) runs two seed-42 Circle 2 runs, about 29s on the Node 22 runner. Its 30s outer timeout SIGKILLed the engine partway through, which surfaced as "Engine exited before response" plus EPIPE. Locally the same test takes about 15s warm and passes on both this branch and main. Correction to the commit message: I did not run main on a GitHub runner, because main has no CI before this PR. Fix: raised that suite's outer budget to 120s and left the per-request 8s timeouts unchanged, so a hung engine still fails fast. Both Node 22 and Node 24 are now green.

@techmore
techmore merged commit c37e015 into main Oct 21, 2026
2 checks passed
@techmore
techmore deleted the improve/ci-and-wire-hardening branch October 21, 2026 21:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant