-
What do you want to achieve? Keep it simple and clear!
I would like to create a sandboxed script environment that will prevent access to services such as DataStoreService, etc -
What is the issue? Include screenshots / videos if possible!
I just don’t know where to start, I do know that it requires the usage of messing with script environment and metatables. Unfortunately I’m not really a expert at script environments and metatables. I also did search the DevForum regarding this topic but it seems like I can’t understand how they work correctly. -
What solutions have you tried so far? Did you look for solutions on the Developer Hub?
Nothing yet for now…
iirc you cant access datastoreservice on the client so u could just set their thing they type to the source of a localscript
The script executor I made supports executing in both client-side and server-side code execution.
You dont need to make a script executor though you can just change a localscripts.Source property
The Source property of LuaScriptContainer (simply LocalScript/Script) requires higher permission level to be read or modified.
What type of sandbox you want to make again? Like how do you want to achieve this? Do you want to just sandbox a script that gets inserted automatically? If so is not possible. In my opinion there is no way to access a script environment unless you call getfenv from within the script or make a module which contains a function that when is called by a script, it changes its environment. But as Roblox said, getfenv de-optimizes the environment so I wouldn’t use it if I was you.
In the script executor for the script builder game I am making, it has:
- A code editor text box
- Execute button
- Button to toggle to execute in the client or at the server
What I want to prevent is to sandbox code generated by players to not allow access to services such as DataStoreService, TeleportService, etc. AND to prevent other players from getting :Kick() or :Destroy() calls
You should try and loadstring the code the player is trying to run. This turns it into a function which in turn allows you to get and set the environment of the function.
To prevent the user from accessing certain services, you could then wrap a instance in which I’d recommend u to look at this tutorial. Wrapping with metatables, or "How to alter the functionality of Roblox objects without touching them"
Here are all objects that you should wrap so you wont have to dig for that yourself: game, Game, Workspace, workspace, Stats
You can’t access Script.Source
on a game script, It can only be used in the command bar and plugins.
Thank you for the link to the post, I will read it,
I use a custom loadstring module with FiOne as the interpreter
You should try and use the standard loadstring function.
The function gotten from that is way faster. Wrapping the objects sacrifice quite some speed already, and using a custom loadstring module might make it even worse.
After all, there isn’t really much of a point to use a custom loadstring module anyway.
Well then, I’ll consider using it then.
- How do I pass the sandboxed environment to the loadstring?
https://developer.roblox.com/en-us/api-reference/lua-docs/Lua-Globals
EDIT:
Awnsered myself, I remember setfenv() 's first argument can be the level or function
Thank you to @alicesays_hallo , @ayyildizlibayrak742 , @focasds , and @CoderHusk for trying to help me,
in the end I used the post that @alicesays_hallo linked and with his suggestion to use Roblox’s loadstring instead of using a custom lodstring module, and it works fine along with I now know how to sandbox code atleast, I appreciate your attempt to help me.
Final code I made:
local envWrapperCache = setmetatable({}, {__mode = "k"})
wrap = function(real)
for w, r in next, envWrapperCache do
if r == real then
return w
end
end
if type(real) == "userdata" then
local fake = newproxy(true)
local meta = getmetatable(fake)
meta.__index = function(s, k)
if k == "Kick" or k == "kick" then
return function(self)
return nil --// https://developer.roblox.com/en-us/api-reference/function/Player/Kick :Kick() returns void
end
end
if k == "Destroy" or k == "destroy" or k == "remove" or k == "Remove" and s == "Player" then
return function(self)
return nil --// https://developer.roblox.com/en-us/api-reference/function/Instance/Destroy returns void
end
end
if k == "Teleport" or k == "TeleportAsync" or k == "TeleportPartyAsync" or k == "TeleportToPlaceInstance" or k == "TeleportToPrivateServer" or k == "TeleportToSpawnByName" or k == "SetTeleportSetting" or k == "SetTeleportGui" or k == "ReserveServer" or k == "GetTeleportSetting" or k == "GetPlayerPlaceInstanceAsync" or k == "GetLocalPlayerTeleportData" or k == "GetArrivingTeleportGui" and s == "TeleportService" then
return function(self)
return nil
end
end
if k == "GetLogHistory" and s == "LogService" or s == "logservice" then
return function(self)
return {}
end
end
if k == "MessageOut" and s == "LogService" or s == "logservice" then
return Instance.new("BindableEvent").Event
end
if s == nil or s == "nil" then
return nil
end
return wrap(real[k])
end
meta.__newindex = function(s, k, v)
real[k] = v
end
meta.__tostring = function(s)
return tostring(real)
end
meta.__metatable = getmetatable(real)
envWrapperCache[fake] = real
return fake
elseif type(real) == "function" then
local fake = function(...)
local args = unwrap{...}
local results = wrap{real(unpack(args))}
return unpack(results)
end
envWrapperCache[fake] = real
return fake
elseif type(real) == "table" then
local fake = {}
for k, v in next, real do
fake[k] = wrap(v)
end
return fake
else
return real
end
end
unwrap = function(wrapped)
if type(wrapped) == "table" then
local real = {}
for k, v in next, wrapped do
real[k] = unwrap(v)
end
return real
else
local real = envWrapperCache[wrapped]
if real == nil then --// wasn't wrapped or doesn't exist.
return wrapped
end
return real
end
end
local env = getfenv(0)
env.OwnerPlr = wrap(properties.plr)
env.game = wrap(env.game)
env.Game = wrap(env.Game)
env.workspace = wrap(env.workspace)
env.Workspace = wrap(env.Workspace)
env.typeof = wrap(env.typeof)
env.rawset = wrap(env.rawset)
env.rawget = wrap(env.rawget)
env.rawequal = wrap(env.rawequal)
--//env.type = wrap(env.type) can create a stack overflow error and crash Studio
env.print = wrap(env.print)
env.Print = wrap(env.Print)
It’s current flaw is that you cannot sandbox the “type” global, attempting to do so creates a stack-overflow error and crashes Roblox Studio