v4.2.0 — close the Luau injection holes, and stop reporting success with nothing in it
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_propertyreported success while returning no value at all for anything but a primitive. The response encoder drops keys holding userdata, soColor,Material,Size,Position,CFrameandBrickColor— 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_luaucould not produce a Studio Undo waypoint. The plugin has always recorded one for any script arriving with anundoLabel, but only generated builders sent one, so the most general write path landed outside the undo stack.propose_next_actionsent 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 "openassetdelivery.roblox.comand 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 carrywithPropertyErrors. - The batch mutation tools accepted a string where an array belongs.
inputSchemais advertised but never enforced server-side, and a string has a.length— somass_create_objectstold 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:checkand 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:checknow 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.