Is there a way to require a module into a custom Lua environment?

Hello, I am trying to require() a ModuleScript into a new environment with no access to the global environment. This is for me to be able to safely sandbox fan made maps for my game where there’s a ModuleScript called Map which is white-listed to run.

In my sandboxing script, I’ve tried:

local moduleScript = script.Module;
local mapEnvironment = {}; -- metatable removed to simplify.
local mod = setfenv(require, mapEnvironment)(moduleScript);

But It doesn’t allow me to change the environment of require.

Then I’ve also tried this:

setfenv(function()
    mod = require(moduleScript);
end, mapEnvironment)();

but this leads to a me having to define require in the custom environment and require() would require the module into the global environment.

I have a feeling that there might be no way around this (correct me if I’m wrong), and I’m currently wondering if a feature where we can set the environment by require(ModuleScript, Environment) is possible?

Thanks in advance
~Khronos

1 Like

At best, you can supply a modified environment to any function that the ModuleScript returns. You cannot modify the environment of the main Module code that runs when it is first required.

2 Likes

As @sircfenner said, you can’t really do that. When you require a ModuleScript it runs its code as a separate script, and the only thing you get is what it returns, but nothing else.

What you can do however is i.e. you can make a sandboxed api which they are forced to use in order to load their map. Or force the module to get sandboxed in order to use the api, by requiring the module to call your function (or your table with __call).

Other than that I doubt there’s anything more that could be done.
My only other idea is that since modules are now open source, you could make a script which would grab the source of the module they input, add your sandbox at the top, then use a webhost to reupload the sandboxed module and then require it. But that would be an overkill.

1 Like

I have thought about something like this myself and despite what people say about loadstring it is actually surprisingly secure if used properly.

Loadstring returns a function from a string which uses the root environment (getfenv(0)). If you want to securely use loadstring (with full Lua functionality), you need a “runner” module with an empty root environment (setfenv(0, {})) and you’ll have that call your loaded function. Your loaded function is set with your sandboxed environment and your runner uses a BindableFunction to execute this sandboxed function in a new thread.

Be careful as this is easy to mess up. Anyone can use getfenv if you give it to them so it’s often times better to never allow them access to this if you don’t want to struggle with securing environments like this.

TL;DR
Loadstring these modules instead of requiring so you can sandbox them before they have run and do not give them getfenv or setfenv unless you’re willing to get complicated and risky with your security.

You can run a script in command bar to convert these modules into their sources and then loadstring them in.

5 Likes

If you have access to source code, then you can simply call this module at the top of any file you want sandboxed.

I haven’t found a way out of this sandbox and I’m planning on using it for a paid service soon. I’ll give anyone who finds a way out of it after calling it at the top of the source file 1k Robux. You must demonstrate it.

Edit: I should note that this script only sandboxes the code that calls it. It may still require other modules which are not sandboxed, but any results passed back into the sandboxed code is protected. This is useful if you wish to allow unprotected libraries for speed.

1 Like

local custom_require = function(module, env)
return setfenv(require, env or getfenv())(module)
end

just use this whenever you want to require a module into a custom environment, you dont need to change require to do this properly, and if you want to send the environment with you just make a new function to change the environment in the separate script

– this is the first script
local m = customrequire(secondscript, {owo = true})
m.changeenv({owo = true})
– second script
m.changeenv = function(env)
for i,v in pairs(getfenv()) do
getfenv()[i] = nil
end
getfenv() = env
end

getfenv().require = customrequire

1 Like

Didn’t test this, but what I’d do is:

  • Get source via Http
  • Create sandbox env
  • Loadstring w/ Rerubi or some bytecode loadstring and pass sandbox env as env

Since every module has to be open now, you should be able to get the source via HTTP and load it using Rerubi or some bytecode compiler. Make sure to also include module’s children.

1 Like

An interesting problem. If I may point out a few things:

I’m not sure how much work your fans put into these scripts or if they are being compensated, but they may not want to give you full access to their code. Perhaps they worked hard on a script and wouldn’t like to leak its source.

On the other hand, you want to make sure their code is secure / doesn’t do anything bad. Since the only way to check that is by having their source, you two may have a conflict or some people may just not make a map for you.

But there is another way to run code securely without having access to its source code. Introducing: RBXMod.com . This project is meant to solve problems like this. From the main page:

By running their code on the RBXMod server, your game is completely secure. To communicate with your game either you can create a private module or they can create a public one which will make calls to their code on the RBXMod server. If the interface is secure, then your game will be secure as well.

Send me a PM if you are interested.

1 Like

Thanks everyone for your suggestions.

I think @Hexcede’s suggestion for my situation may be the most feasible as I can write a plugin for mappers to convert their Map ModuleScript into a StringValue and let the server sandbox the loadstring content.

Thank you @IdiomicLanguage for the Lua-Sandbox.

@len_ny I’ve tried that, either I did it wrong or it’s not possible to change the environment of require(). Doing so gives me this error. Error: 'setfenv' cannot change environment of given object