Skip to content

v4.2.0 — close the Luau injection holes, and stop reporting success with nothing in it

Choose a tag to compare

@princeofscale princeofscale released this 07 Aug 21:10
· 51 commits to main since this release
7ed782d

Security

Eleven caller-supplied values across seven builder tools reached generated Luau without being escaped, so a crafted argument executed arbitrary code in the Studio plugin's edit context.

This mattered more than the count suggests: none of those tools declares the studio.execute effect. That effect is what the builder profile filters on — the profile README describes it as "arbitrary Luau execution denied" — and what the execute_luau safety gate hangs off. Both were bypassed by tools that promise neither.

Affected: template_create_simulator_game (currencyName, inside a Luau string literal, so a quote closed it), template_create_tycoon_game, template_create_round_game, template_create_obby_game and environment_create_day_night_cycle_script (numeric arguments typed number but never checked at runtime, so a JSON string was emitted as code), plus apply_theme and design_lint (minTextSize). environment_create_day_night_cycle_script also placed scriptName inside the [==[ … ]==] literal holding a generated script's Source, where a name containing that delimiter ended it early.

Every value now routes through luaString/luaNumber. Regression tests drive each builder with a string that closes its quote, a name that closes the long bracket, and a "number" that was never a number — and the detector itself is tested, because a marker that vanishes along with the literals it hides in proves nothing.

Upgrade if you expose these tools to untrusted input.

Added

  • get_spatial_layout — where things physically are. Every other scene read answers a question about the tree; none answered the one you have to settle before placing a single part: how big the built area is, where the ground is, and which patch is empty. Measured 19.8× cheaper than the read pattern it replaces (651 vs 12,851 tokens on 121 objects; 20,000 parts in under a second).
  • asset_fit_plan / asset_fit_apply — measures how a model sits in the scene against a Roblox character, and applies scale and pivot as one undo waypoint. Scale is absolute against the authored size, so applying twice does not compound.
  • asset_sanitize_plan / asset_sanitize_apply — reports what the scripts inside a model you did not write actually do, then disables or removes them. Script source is never returned.

Fixed

  • mass_get_property reported success while returning no value at all for anything but a primitive. The response encoder drops keys holding userdata, so Color, Material, Size, Position, CFrame and BrickColor — most of what you batch-read while building — came back as successes with nothing in them. Values are now tagged; primitives still pass through as primitives.
  • execute_luau could not produce a Studio Undo waypoint. The plugin has always recorded one for any script arriving with an undoLabel, but only generated builders sent one, so the most general write path landed outside the undo stack.
  • propose_next_action sent agents to fix a script that does not exist: it pulls dotted names out of error text, and a URL host is a dotted name. An episode whose errors were all asset fetches came back as "open assetdelivery.roblox.com and fix it" — a self-driving loop with no exit.
  • Batch create and duplicate summaries reported {succeeded: 4, failed: 0} next to properties the engine had rejected. They now carry withPropertyErrors.
  • The batch mutation tools accepted a string where an array belongs. inputSchema is advertised but never enforced server-side, and a string has a .length — so mass_create_objects told the safety gate "create 24 objects" and wrote "created 24 objects" into the operation history for a batch that never existed.

Changed

  • The Luau this server generates now runs under Lune against a real DataModel, in release:check and in CI. Generated Luau is where the read tools compute their answers, and none of it was reachable from Jest — it could only be checked by hand against a live Studio, which in practice means checked once and then never again.
  • release:check now runs the 10,000-request fault-injection benchmark. It used to live only in a separate alias, so a green local check could still fail CI.

Full detail, including the reasoning behind each fix, in CHANGELOG.md.