Skip to content

October 2026: second review ​

On 28 October 2026 we ran a second full review of MMO Run, a day after the fixes from the first review landed. It covered the game server, the client, the launcher, the relay, Run Market, the websites, the deploy files and the build pipeline: 16 areas, each read by two independent reviewers per kind of weakness, plus two broad sweeps and a search for committed secrets. Every candidate finding then had to survive a panel of three independent checkers (can it be reached, does it matter, what already stops it), and only findings at least two of the three confirmed were kept.

The review produced 38 confirmed findings. Several described the same weakness from two sides (the code that sends something and the code that receives it), so they are listed here as 28 issues. All of them are fixed in MMO Run 0.6.0 and relay 0.3.0, and every fix comes with tests that try the attack and expect it to fail.

This review found its issues by reading the code. Two of them (MR-2026-43) depend on how the game engine handles a corrupt pack file, which we did not confirm by running it; we fixed them regardless.

Who was affected. Run Market was not yet online, so no one could publish or download packs through it. The relay at relay.mmo.run was live; its relay fixes take effect there with relay 0.3.0, and its /healthz shows the version and build it runs.

Severity ​

The same scale as the first review:

SeverityMeaning
CriticalLets someone run code on other people's computers.
HighExposes passwords, takes a public service down with little effort, or lets an admin take over a server.
MediumLets a player gain powers or abuse a server they should not, or makes a real attack much easier.
LowLimited impact, needs unusual conditions, or is hardening.

Summary ​

IDSeverityAreaFindingStatus
MR-2026-42HighPacks"Data only" content packs could run database statements on the worldFixed
MR-2026-43CriticalClientA server could hide scripts from the client's pack checkFixed
MR-2026-44HighRun MarketOne crafted upload could stall the registry for everyoneFixed
MR-2026-45HighRun MarketWhoever re-registered an abandoned GitHub name inherited its packagesFixed
MR-2026-46MediumServerA player could steer a ban or kick onto someone else's accountFixed
MR-2026-47MediumServerA relay-side trick gave relayed players a fresh limit key per connectionFixed
MR-2026-48MediumLauncherOn Windows, importing a world zip could write outside the worlds folderFixed
MR-2026-49MediumLauncherAn over-long relay reply could run a console command as the ownerFixed
MR-2026-50LowRelayThe invite's secret crossed the network in the clear every 15 secondsFixed
MR-2026-51LowServerLogging out did not end the session on the serverFixed
MR-2026-52LowRelayRevoking an invite revealed whether a guessed code existed, without a rate limitFixed
MR-2026-53LowServerAn admin could stall the server with an oversized map in the editorFixed
MR-2026-54LowServerAdmins could teleport staff of equal or higher rankFixed
MR-2026-55LowClientPack code approved for one server could be reused by anotherFixed
MR-2026-56LowPacksA pack's script path could point into another packFixed
MR-2026-57LowRun MarketA README could cover its package page with a fake oneFixed
MR-2026-58LowClientA server could make the client open any of the game's own screensFixed
MR-2026-59LowServerA world's private key could be read by other users of the computerFixed
MR-2026-60LowLauncherA pack could give itself the "official" badgeFixed
MR-2026-61LowClientA server could freeze a joining client with an enormous mapFixed
MR-2026-62LowRun MarketTwo first publishes at once could put a version under another authorFixed
MR-2026-63LowServerA login during a password reset could survive the resetFixed
MR-2026-64LowServerA username could end in an invisible line breakFixed
MR-2026-65LowServerEditor god mode outlived losing editor accessFixed
MR-2026-66LowGameplayScripts gave rewards even when the cost could not be takenFixed
MR-2026-67LowGameplayTalking to an NPC repeatedly could repeat a one-time rewardFixed
MR-2026-68LowGameplayNearby chat was replayed to players anywhere in the worldFixed
MR-2026-69LowGameplayA freed character name inherited its old friendshipsFixed

Details ​

MR-2026-42: "data only" content packs could run database statements (High) ​

What was wrong. Content packs are presented as data that is safe from anyone, yet the world ran the SQL migrations a content pack shipped. The check meant to keep a pack's statements to its own tables could be bypassed with unusual quoting or comments, and some statement types were not checked at all. A pack author could make themselves the world's owner, change any account's password, or write database files elsewhere on the host.

What changed. SQL counts as code everywhere. Content packs and style packs may not carry any, and they never run migrations. An extension's migrations are read with a tokenizer that follows SQLite's own rules, may only create, change or fill the extension's own tables, and may not use triggers, views, attached databases, settings or transaction control. The world also compares its database layout before and after each migration and rolls back if anything outside the extension's own tables changed. Run Market refuses content packs and code-free extensions that carry SQL, and the launcher warns before installing any pack with code.

MR-2026-43: a server could hide scripts from the pack check (Critical) ​

What was wrong. Before using a pack a server sends, the client reads it to make sure it holds only data in its own folder. One field of the pack's header was read differently by that check and by the game engine, so a deliberately corrupt pack could show the check one list of files and the engine another. A hostile world could have run code on a joining player's computer without the consent prompt. We did not confirm the engine side by running it.

What changed. The client reads the pack's file list only where the engine does, refuses any header field that is out of range or not zero where it must be, and refuses duplicate paths, entries outside the file and hidden bytes in file names. Real packs made by the game's own tools are unaffected. Run Market applies the same rules to uploads.

MR-2026-44: one upload could stall Run Market (High) ​

What was wrong. Two of the checks Run Market runs on an uploaded pack took time that grew with the square of the input. Any signed-in author could upload a pack that kept the registry busy for minutes or hours, during which nobody could browse, download or sign in.

What changed. Both checks now take time in proportion to the upload, with limits on file counts, path lengths and the size of what is scanned. The rest of the registry was searched for the same kind of pattern.

MR-2026-45: re-registered GitHub names inherited packages (High) ​

What was wrong. Run Market accounts were keyed on the GitHub user name. GitHub releases a name when its owner renames or deletes the account, so whoever registered it next could sign in as the old author, publish new versions of their packages, and keep any trust level the old account had, including official.

What changed. Accounts are tied to GitHub's permanent numeric account id. Renaming on GitHub carries your account along; someone who later registers your old name is refused. Official accounts are listed by id.

MR-2026-46: bans and kicks could hit the wrong account (Medium) ​

What was wrong. /ban and /kick looked a name up as a user name before a character name, and a character could be named after someone else's user name. A player could make the host's own Ban button in the launcher ban a moderator, while escaping bans themselves.

What changed. A command's target names exactly one account: if a name could mean two, the command asks which. Character names and user names can no longer collide, and the launcher acts by account and connection id rather than by name.

MR-2026-47: per-address limits could be reset through the relay (Medium) ​

What was wrong. By opening and abandoning connections through the relay in a particular order, a stranger could fill the host's table of player addresses, after which every new relayed player looked like a new address. That disabled the login lockout, the cap on new accounts and the cap on connections per address.

What changed. Entries in that table expire and are removed when a player is gone, and relayed players whose address is not known yet all share one limit.

MR-2026-48: world zips could write outside the worlds folder on Windows (Medium) ​

What was wrong. The launcher named an imported world after the top folder inside the zip and did not check that name. On Windows, a zip whose folder name used backslashes and .. could put its files anywhere the user could write.

What changed. The folder name must be a valid world name, zips with backslashes or drive letters are refused, and every file written must stay inside the new world folder.

MR-2026-49: an over-long relay reply could run a console command (Medium) ​

What was wrong. The server read the launcher's control channel in fixed-size pieces and treated each piece as a whole line. An invite reply from a malicious or compromised relay could be long enough that a console command landed at the start of a piece, where it ran with the owner's authority.

What changed. The server acts only on complete lines and drops any line over 16 KB whole. The launcher and the server check that an invite's code, secret and relay address have the exact shapes the relay issues before storing or forwarding them.

MR-2026-50: the invite secret crossed the network in the clear (Low) ​

What was wrong. A world proved to the relay that it owned an invite by sending the invite's secret, unencrypted, every 15 seconds. Anyone who saw one of those messages could take over the world's relay route, learn relayed players' addresses, or cancel the invite. Game traffic itself stayed encrypted.

What changed. The secret never crosses the relay's UDP port. The world answers a fresh challenge from the relay with a key derived from the secret; answers cannot be replayed and are useless from any other address, and only a fresh answer can move a session to a new address.

MR-2026-51: logging out did not end the session (Low) ​

What was wrong. Log out only forgot the saved login on the device. Someone who had copied it could keep using it for up to 30 days.

What changed. Log out ends the session on the server too, so a copied login stops working. Other devices stay signed in.

MR-2026-52: invite revocation revealed which codes exist (Low) ​

What was wrong. Looking up invite codes is rate limited because codes are short, but the revocation route answered differently for a code that exists and one that does not, with no limit, which made guessing codes much faster.

What changed. Revocation gives the same answer for an unknown code and a wrong secret, and shares the lookup rate limit.

MR-2026-53: an oversized map could stall the server (Low) ​

What was wrong. The editor rejected maps over 512 by 512 tiles, but still built the rejected map before saying so. An admin could make the server run out of memory or hang.

What changed. The editor stops at the first validation error, and the game never builds a map larger than the limit, wherever it comes from.

MR-2026-54: admins could teleport higher-ranked staff (Low) ​

What was wrong. Moving another player with /tp did not check rank, unlike kick and ban.

What changed. It does now: staff can only move players below their rank.

MR-2026-55: pack code approved for one server could run for another (Low) ​

What was wrong. Packs stay loaded until the game is closed. A second server could list a pack with code the player had approved for a different server, claim it held no code, and have it run without asking.

What changed. The client records what each loaded pack holds from its bytes, not from what a server claims, and reuses a pack with code only where the player approved it.

MR-2026-56: a pack's script path could point into another pack (Low) ​

What was wrong. A pack's manifest names its scripts by path, and the path was not confined to the pack's own folder. A code-free pack could run a script from another loaded pack.

What changed. Script and config paths must stay inside the pack's folder, and the game checks a script is an extension of the right kind before creating it.

MR-2026-57: a README could fake its package page (Low) ​

What was wrong. Run Market allowed inline styles in package READMEs, so an author could lay a convincing fake page, with a fake trust badge and download button, over the real one.

What changed. READMEs may use plain formatting only; styles and layout attributes are removed, and the README area cannot draw outside itself.

MR-2026-58: a server could open the game's own screens (Low) ​

What was wrong. The in-game editor's client half let the server register entries in any of the client's lists, including the list of screens to show. A server could make the client open, for example, the host launcher and start the player's own worlds.

What changed. The editor only accepts the content lists it edits, and the client only shows screens that belong to the extension that registered them.

MR-2026-59: a world's private key could be read by other local users (Low) ​

What was wrong. A world's private key was briefly readable by other accounts on the same computer when it was created, and lost its protection when the world was imported.

What changed. The key is created private, is made private again every time the world starts, and is restored to private after an import (with the world database and invite). On Windows it keeps the user profile's permissions.

MR-2026-60: a pack could give itself the "official" badge (Low) ​

What was wrong. The launcher showed a pack as official when the pack said it was, rather than when Run Market's signature said so.

What changed. For anything not shipped with the game, the badge comes only from Run Market's signature.

MR-2026-61: a server could freeze a joining client (Low) ​

What was wrong. The client built whatever map size a server sent, so a hostile world could make it run out of memory or hang on join.

What changed. The client checks every map against the same limits the server uses and returns to the menu instead.

MR-2026-62: concurrent first publishes could mix up owners (Low) ​

What was wrong. Run Market checked who owned a package before writing the new version, in a separate step. Two first publishes of the same name at the same moment could put one author's version under the other's name and trust level.

What changed. Ownership is checked in the same database write that stores the version.

MR-2026-63: a login during a password reset could survive it (Low) ​

What was wrong. A login made with the old password while an owner reset it could finish after the reset and get a new session.

What changed. A login fails if the password changed while it was being checked.

MR-2026-64: user names could end in a line break (Low) ​

What was wrong. The user name check accepted a trailing line break, so a second account could look identical to an existing one in the launcher.

What changed. User names are checked exactly: letters, digits and underscores only.

MR-2026-65: editor god mode outlived losing editor access (Low) ​

What was wrong. If a staff member lost editor rights while the editor was open, they kept being invulnerable and walking through walls until they closed it.

What changed. God mode ends the moment editor access does.

MR-2026-66: scripts rewarded players who could not pay (Low) ​

What was wrong. A Run Script take succeeded even when the player had dropped the items or gold while a dialogue was open, so the reward after it was handed out for free.

What changed. take and take_gold are all or nothing, and stop the script when the cost is missing.

MR-2026-67: repeated talking could repeat a one-time reward (Low) ​

What was wrong. Each conversation with an NPC started a new script without ending the last, and a click answered whichever was oldest. A fast client could collect a one-time reward many times.

What changed. A player has one conversation per NPC, and every click answers the conversation it was shown for.

MR-2026-68: nearby chat was replayed world-wide (Low) ​

What was wrong. Local /say chat is only sent to nearby players, but it was kept in the history shown to anyone entering the world, in any zone.

What changed. Local chat is not kept in that history, and lines already kept are filtered.

MR-2026-69: freed character names inherited friendships (Low) ​

What was wrong. Friend lists stored character names. When a character was deleted, whoever took the name next became a mutual friend of everyone who had added the old character, and could see their online status and location.

What changed. Friend lists store characters by their permanent id, and a deleted character is removed from every list. Lists saved before this change were converted using the current owner of each name.

What we recommend to hosts ​

  • Use MMO Run 0.6.0 or later. If you run your own relay, update it to 0.3.0 before your hosts update: new worlds cannot register with an older relay.
  • Install community extensions only from authors you trust; extensions with server code or database changes run with your permissions.
  • Use the target forms user:, char: or account: when a moderation command asks which player you mean.

MMO Run: host your own pixel-art MMO.