Skip to content

Let commands be server-only and let host code reset departed players - #1854

Merged
Noisemaker111 merged 1 commit into
mainfrom
claude/jgengine-upgrade-llm-tycoon-fkceto
Sep 25, 2026
Merged

Noisemaker111 merged 1 commit into
mainfrom
claude/jgengine-upgrade-llm-tycoon-fkceto

Conversation

@Noisemaker111

Copy link
Copy Markdown
Owner

This came out of an LLMville audit. There, a client could send startTraining with forged unlock and multiplier inputs through the public runCommand, and an insolvency reset for a player who had left threw and rolled back a whole 25-game billing batch.

  • CommandDef.access: "server" (core). The public Convex runCommand mutation and WebSocket client messages now refuse such commands with SERVER_ONLY_COMMAND_REASON. helpers.runCommand and host.runCommand({ trusted: true }) still run them. The default is "client", so existing games are unchanged.
  • GameRuntime.commandAccess(name) reports a command's access.
  • helpers.resetPlayerProfile now resets a player who left but still has a profile. A user with no membership and no profile still throws.
  • The jgengine-multiplayer skill now says to treat command input as client-controlled and to use nowMs, not a now field in input.

Closes #1853

Bugfix notes

  • Root cause: every registered command was reachable from any client, and nothing marked which inputs are host-decided.
  • Any game on the published SDK can hit the same class of bug; access: "server" now covers it.
  • Why the default stays "client": flipping it would break existing probe games.

Verification

  • bun test packages/convex packages/ws packages/core/src/runtime: pass. New tests cover refusal and trusted runs on both hosts, and resetting a player who left.
  • check-types for core, convex and ws: pass.
  • bun run gen: done; ship:preflight: ok.
  • There's no rendered surface to screenshot.

🤖 Generated with Claude Code

https://claude.ai/code/session_01GvRbbu8yG1WYz7wQnZUpC8


Generated by Claude Code

A command whose input carries host-decided facts could be sent by any client
through the public runCommand, so a game had to remember to strip it from its
exports. CommandDef.access "server" makes the public Convex mutation and
WebSocket client messages refuse it while helpers.runCommand and trusted host
calls still run it.

resetPlayerProfile threw for a player who had left the server, which aborted
whole scheduled batches; it now resets anyone with a profile.

Closes #1853

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GvRbbu8yG1WYz7wQnZUpC8
@Noisemaker111
Noisemaker111 enabled auto-merge (squash) September 25, 2026 20:21
@Noisemaker111
Noisemaker111 merged commit dc9084f into main Sep 25, 2026
6 checks passed
@Noisemaker111
Noisemaker111 deleted the claude/jgengine-upgrade-llm-tycoon-fkceto branch September 25, 2026 20:25
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.

[FEATURE] createGameServerFunctions: let a runtime mark commands server-only so public runCommand can't be sent them

2 participants