Allow the Command Line to access the Client/Server's _G

As a Roblox developer, it is currently too hard to inspect and modify your game code’s Model state. If the command line were allowed to access _G again, it would be extremely easy to do this, as you could shove a reference to your Model objects into _G for you to poke and prod at to your heart’s content.

Example of what I want to do, have my main Model class do this in it’s setup:

_G.CurrentState = this

Then later while debugging. Hm… I can’t move this unit for some reason. Is it my UI logic or my model logic that’s gone wrong? … no problem:

unit = _G.CurrentState:GetUnits()[1]
_G.CurrentState:UnitMove(unit, 1, 1)

This was possible in the past when I was last developing games and I used it literally all the time, like, multiple times an hour. It’s really frustrating to try to get by without it.

I can’t see why any reason why this behavior shouldn’t be allowed. The command line is fully under the control of the developer, there’s no additional conflicts created that don’t already exist in the _G namespace whether the command line has access to it or not.

Edit: Since the response is that the inter-security level interaction isn’t changing. Can we have a switch / option to drop the command line to the same security level as the scripts?

14 Likes

I don’t think this is possible (anymore, if it was ever the case like you mention) because the command bar runs on a different Lua identity level than the game scripts. They run in different environments, so their _G/shared are disconnected from each other.

1 Like

Unless I’m mistaken and it’s changed in the meantime, it’s threads that run at security levels. There is no “security level of the environment”, there’s just a security level of each individual thread of execution.

  • If you start a thread from the command line then it would execute the game code at CommandLineSecurity level, but that’s okay, because that’s what you just asked it to do.

  • If you start a thread in game that calls code you defined on the command line, it will execute at ScriptSecurity level, and it can’t do anything bad.

I don’t see where the vulnerability is.

1 Like

This is totally possible; they run under different Lua states but they can still share references to one another. The reason it’s not possible to begin with is likely an overlook – when creating a new, higher context thread they inherited from a different state’s environment.

I imagine one reason Roblox has kept security levels completely separate is because they want to minimize any chance of normal scripts jumping up security levels. To my understanding, it’s not possible currently, because it’s unimplemented. If it were possible, then it’s also possible that there’s a bug that could be triggered to escalate the security level. As unlikely as that is, the current structure minimizes the chance of such a bug occurring.

An interesting alternative is to have a security context option for the command bar. When messing with scripts and their environment, set it to the “Script” security context. When messing with game/studio stuff, set it to the “Studio” security context.

1 Like

Whatever the technical reasons behind the scenes, they are just that though: Internal technical reasons, and I don’t imagine they’re insurmountable ones either. My point stands that I can’t see any practical reason why it shouldn’t be possible.

I mean, I have a command line right in front of me… and code running right in front of me that I want to edit… it simply makes no sense that I can’t do it, there’s a frustrating arbitrary barrier there for seemingly no good reason.

I wonder if there even needs to be a separate security level for the command line, can’t it just run at script security? Is there anything people do from the command line that a normal script shouldn’t be able to do?

1 Like

The first thing that comes to mind in my case is game:GetObjects, which I use to insert anything that acts like a model – decals, shirts, pants, hats, gear, or even models: it’s easier than InsertService.

Scripts don’t have access to Selection:Get, Selection:Set, or ChangeHistoryService:SetWaypoint. I use those often.

The command bar has access to settings(). I don’t use this, but I can see someone using this to toggle properties like ShowDecompositionGeometry.

Each security context has its own set of global tables. That’s unlikely to change in the near future because scripts running in different security contexts shouldn’t directly interact with each other.

All API members marked PluginSecurity, RobloxPlaceSecurity, or LocalUserSecurity.

1 Like

In that case, can we change the question to: Can we add a switch/option to drop the Command Line to Script Security level instead?

Do you agree with my point that the Command line should, logically speaking, be able to interact with scripts?

1 Like

I’ll let Studio answer the first question.

IMHO, there could be some value in it as a debugging tool.
I don’t think global tables are a good place for scripts to interact, but that’s different.

As a bandaid, you can bridge the gap with Bindables:

local bridge = Instance.new("BindableFunction")
bridge.Name = "Debug"
function bridge.OnInvoke(object, method, ...)
	return _G[object][method](_G[object], ...)
end
bridge.Parent = workspace
unit = workspace.Debug:Invoke("CurrentState", "GetUnits")[1]
workspace.Debug:Invoke("CurrentState", "UnitMove", unit, 1, 1)

Keep in mind that tables are copied as they cross the bridge.

3 Likes

For me personally, I don’t like to use _G because you can’t be sure that functions and variables will be there when you try to ask for them.

If you really want to be able to view your game’s state as it is from memory in Lua, you should take a look at the Lua Debugger: http://wiki.roblox.com/index.php?title=Lua_debugger

The feature has drastically improved since its inception. Watching a dictionary/array allows you to expand its contents.

Instead of using _G, you really should use ModuleScripts. ModuleScripts are only executed once per context level, so they’ll return the same state each time they are required unless you’re returning a constructor function.

I know it’s a pain to traverse the DataModel on the client, I even made a thread suggesting that we should be able to load ModuleScripts by name when we’re using the LinkedSource feature. It’s in your best interest to cooperate with Roblox’s API quirks though, because it’ll save you a lot of headaches down the road if you just get used to it. There are more practical solutions to problems like these than opening up _G to be shared by scripts and the command bar.

This problem has more to do with live debugging, and being able to call APIs on the current game state. You could make the state accessible through a module instead of _G, but that doesn’t really change much. It doesn’t solve the identity boundary problem either (as explained here). For the case of interactive debugging, I’d say the use of _G is perfectly fine, since it already isn’t used in any other way, and there are no scripts waiting on it.


Also, I thought of another nice bandaid that only works while the game is running. Since both plugins and the command bar have more privileges, they can read and write script sources. This can be used to run input within a regular script.

function _G.run(s)
	local script = workspace:FindFirstChild("CommandScript")
	if not script then
		script = Instance.new("Script")
		script.Name = "CommandScript"
		script.Parent = workspace
	end
	script.Source = s
	script.Disabled = false
	script.Disabled = true
end

Despite plugins having a different identity from the command bar, they still share the same environment. This means the run function can be set up by a plugin, then used by the command bar.

_G.run[[unit = _G.CurrentState:GetUnits()[1] ]]
_G.run[[_G.CurrentState:UnitMove(unit, 1, 1)]]
2 Likes
local Fenv = getfenv(0)

function Bindable.OnInvoke()
    return function()
        return Fenv
    end
end

And then from the command bar…

local Fenv = Bindable:Invoke()()

_G = Fenv._G

I can’t test this right now but I’d assume it works. BindableEvent can pass functions and Event receive it, so using upvalues and BindableFunctions shoud achieve the same.

We definitely won’t allow scripts running at CommandLineSecurity to access the Lua environment from ScriptSecurity because that would open the door for vulnerabilities. But having a way to run code from the command bar at ScriptSecurity seems reasonable.

If you’re running a game you can achieve this by opening the Developer Console, switching to the Server Log tab, and entering your Lua code.

2 Likes

Yes I was aware, but upvalues are handled via stack position reference, which means calling that function would return the real table.

Just checked, though, and apparently we can’t invoke from BindableFunctions from a higher context at all?

You Invoked the BindableFunction just fine, but the inner function you returned became nil, because there’s no way to transfer it to the command bar environment, because they’re completely separate environments.

Woops, my bad, I assumed it meant nil as in the OnInvoke at first glance. Although it seems a bit weird that this happens; Lua has no actual limitations for these sorts of things so they must have been implemented manually – the function returning and table copying I mean. Wasn’t the whole original point that RemoteEvents were JSON-able, so Bindables were made the same, anyways?

As far as I know, there shouldn’t be any security flaws anyways. If we can use the commandbar at the elevated context then we can do everything a normal script can, and more. So opening up the ability to interact with the default state seems more like an occasional inconvenience in contexts vs a security flaw.

Awesome, this is a good enough shim for me for the time being, I didn’t know that plugins could shove stuff into _G so it’s pretty painless with that in mind. I think it actually takes a bit more work than you show there to hook up the “unit” variable between the two calls, but I can do that with some setfenv magic.