H6x - Secure Luau Sandboxing Library (Moved to GitHub)

Cool :smile:
Do be wary though that you are allowing users to run code on the server with this tool, so there are still large risks around it. It’s important to note what sort of potential someone would have if they escaped the sandbox either because of something you allowed or a bug/exploit in the sandbox.

For example, if a big enough exploit were found in the sandbox, it could be possible for someone to get access to things like DataStores, MemoryStores, or MessagingService, and that could be impactful to your game.

It would be no different than what a backdoor could do in many ways, so, keep that in mind. I wouldn’t recommend using this unless your game concept involves writing lua code or if the risks of it are not very big for you or your players.

1 Like

This is top notch. I’ve always wanted to know how to make one of these!
Can you give some examples of an over-simplified sandbox (like the code to it)?

That would be really helpful. No plans on making one, doubt I’ll need it. But it’s pretty interesting and I’d like to know how it’s done and how all of the security bugs are patched.

Hey! How do i blacklist all roblox datatypes and things like game and setfenv?
If its up there and i didnt see it im very sorry :slight_smile:

You can blacklist the Instance type. As for things like setfenv, you can blacklist the function by reference, and/or remove it from the environment. There are also existing premade sandboxes which already exclude instances, for example (but not setfenv/getfenv).

Oh thank you! also where i find that pre-made sandboxes? Also if i create my own env it will exclude other things?

Good job, however are you planning to make a UI framework to this?

Is there any way I can execute ModuleScript with it and then export the module object?
The point is, you can have code that runs on require, the table can be checked but script content itself, not really.

I tried using require in function (LoadFunction) but code in module script can still access game and such even though I’ve created empty environment.

I apologize for not replying earlier. IIRC the pre-made sandboxes can be created with Environment. I don’t remember very well, unfortunately. And, yes, if you create your own environment (e.g. an empty one), it won’t have access to anything that you don’t give it access to.

Sorry, I don’t understand. A UI framework? I’m a little confused how UI is related to this.

Unfortunately, no, not without a way to grab the source code from the module so you can turn it into a function via loadstring.

Yep, LoadFunction sandboxes a function, but, require loads the module with a new environment.

1 Like

Sadly it’s not possible. Do you know any other way I can prevent code that is outside of any function to be executed? I guess I will need to make something like string value instead of module script

How would I implement vLua VM into H6x? I’ve tried replacing the loadstring function to use vLua for simulating bytecode strings. Once I implemented this idea, the module ran into an error while running the tests:

[H6x] An error occured while running test H6x.Tests.Run:
	H6x.Tests.Run:4: Failed to return (true)
H6x.Tests.Run:4
H6x:2339 function RunTests
H6x:2384
  -  Studio
[H6x] An error occured while running test H6x.Tests.Yield:
	H6x.Tests.Yield:10: H6x:1752: @compiled-lua:1: H6x:1551: table index is nil
LoadstrX.FiOne:544 function on_lua_error
LoadstrX.FiOne:1057 function wrapped
H6x:297 function export
H6x:297 function export
ServerScriptService.ServerRunner.Runner:59

H6x.Tests.Yield:10
H6x:2339 function RunTests
H6x:2384

I don’t think this is possible, sorry, and, I’ll mostly only focus on security issues that are brought up. I was wanting to redo this project, but just haven’t had time.

vLua is a very minimal VM styled after lua, its not fully featured, so its bound to have some compatibility issues with code like H6x’s that uses a lot of complicated features. But, on the bright side, this also means that there’s not much real benefit to using anything like this project, since this is meant for the opposite kinds of cases where you want to have a lot of lua features that work.

Tl;dr you should use vLua on its own, you won’t get many benefits from H6x that vLua wouldn’t already provide just being built the way it is.

1 Like

Hello there! I have two questions regarding H6x.

  1. Can I somehow blacklist usage of Parent so the script can only access an object and its children but not object’s parent(s)
  2. How can I return objects from the script using return? When I try to do so I got some Userdata(...), what should I do with it? Or should I do something else than just
local x = sandbox:ExecuteString("blah blah blah")

You could whitelist only the instances you’d like them to access.

This works basically just like loadstring does (that’s what’s going on under the hood anyways). You can have your code return something and it should be returned in the call.

However, be somewhat careful about how you use any returned values. If you give users access to metatables, or you plan to directly call any functions, keep in mind that you would potentially be exposing the environment to your untrusted user code if you go around activating that code.

Is this a project you plan to continue at this point?

I’m considering using sandboxing in my game but my use case is likely very risky. I’d like to provide users with a way to own and manage their own servers in my game and exposing the game’s framework API to them so they can create mods for the game. I’m not very well versed in what might create a potential danger and allow users to escape the sandbox so I would likely have many questions.

One right off the bat: is what I’m considering doing with H6x a horrible idea? My entire framework is module based and works off of overriding the require function to return a more friendly OOP structure for the game. Currently none of the framework interacts with DataStore, MemoryStore, or MessagingService. I’d likely start off with the Empty environment template provided.

My initial thought is that I would create a secondary API layer that interacts with specific parts of the game’s framework rather than giving the user access to the framework itself. Could I provide a global reference to the API similarly to the built in global references in Roblox scripts such as game, workspace, etc?

Is there a discord server for this?

Due to general life stuff I have not been paying too much attention to many of my projects, or trying to update or improve them a whole lot. However, I would absolutely love to move this project over to GitHub as a Rojo project and start updating and accepting contributions and all of that, possibly sometime soon, as I can see this tool being very useful in various niche areas. If any notable security concerns with H6x are brought up, I will absolutely still update the tool, however, the tool is not used by very many people so it isn’t really battle-tested.

This is actually the exact use case I originally wanted to create this tool for :smile:

You could do this, and, this would likely be the “proper” way under H6x. If you give them direct access to your game’s APIs, you would have to avoid letting them set or change any stuff you don’t want them to. It’s a lot easier (and a lot more secure) to have a separate API that, if they do end up modifying it, or breaking it, won’t effect the rest of your game.

H6x currently isn’t using some of the new features in luau which would greatly benefit its usability (and in plenty of cases, security). For example, coroutine.close, table freezing, etc. I plan to address this at some point, it’s been on my mind since each of these features started releasing (maybe even soon)

(Here are some generally security recommendations/considerations I have for your use case)

Unless you absolutely can’t for some reason, avoid directly calling user-defined functions, instead, let them call yours, or run their code indirectly inside the sandbox. H6x intends to prevent traversing function environments from direct calls, but, it is generally just better practice not to do so if you don’t need to since it still provides some amount of access. If you want to run a user script once, you should use Sandbox:ExecuteFunction() (or ExecuteString which is equivalent to calling the LoadString method and passing it to ExecuteFunction). If you have an already sandboxed function (via LoadString or LoadFunction to overwrite its environment and therefore its access to any globals and such), you can safely use BindableFunctions and/or BindableEvents to invoke this user-code in an isolated way. Both of these will spawn their code into a new thread and Roblox tracks it entirely separately (Roblox actually does this in core scripts for things such as the reset button callback)

Of course, try to limit giving access (even indirectly) to core pieces of your game from user-defined code. If you, for example, want to provide a way for a user to add a new item (assuming you have some kind of “item registry” or something), you don’t want them to be able to access any values, or change any functions that your other code might end up using for some reason, because that allows them to modify further behaviour (and potentially work their way out of your code)

So, essentially, you only want to provide the bare minimum for what your code needs to function.

A sandbox could much more easily abuse code like this:

function ItemRegistry:AddItem(itemDescription)
	self.Items[itemDescription.itemId] = itemDescription
end
-- Considerations:
-- itemDescription can have a metatable (allowing code to run when accessing values)
-- code could overwrite ItemRegistry.Items
-- code could access other items via ItemRegistry.Items and modify them directly (potentially in unintended ways)

-- While one improvement could be to :Blacklist() or :Redirect() ItemRegistry.Items, there still may be other things you want to consider

But a structure like this allows you to have more fine tuned control:

function createItemRegistryAPI(RealItemRegistry)
	function ItemRegistry:AddItem(itemDescription)
		assert(type(itemDescription) == "table", "ItemRegistry:AddItem() only accepts a table as its 1st argument.")

		-- Create a copy of itemDescription without a metatable
		local itemDescription_Safe = table.create(#itemDescription)
		for index, value in pairs(itemDescription) do
			itemDescription_Safe[index] = value
		end

		-- Maybe do some validation (e.g. prevent overwriting other items)
		assert(not RealItemRegistry.Items[itemDescription_Safe.itemId], string.format("An item is already registered with the id %s", itemDescription_Safe.itemId))
		
		-- Now call the real function
		RealItemRegistry:AddItem(itemDescription_Safe)
	end

	return ItemRegistry
end

-- Pass the real ItemRegistry and create one for user code inside the sandbox that'll indirectly use ItemRegistry
local ItemRegistry_Safe = sandbox:ExecuteFunction(createItemRegistryAPI, RealItemRegistry)

-- Some further considerations:
-- Maybe the user passes other data than you might normally expect. For example, you might expect a string, but they might pass a table with metatables on it, and depending on your code this might allow them to do things you don't want (e.g. spoofing the string length, or changing a method)

While H6x does a lot to prevent access to stuff you don’t allow, that doesn’t mean that you can’t accidentally provide and allow them to use something that they can take advantage of to do things you didn’t anticipate, so, generally, you just want to be a bit wary.

Not currently, but, I’d be happy to make one, in fact, I think this would be great :smile:

1 Like

Thanks for the awesome reply. I was not expecting to see so much encouragement for the use case. Now I’m really pumped to do this! Thanks for all of the advice and for the hard work you’ve put into this module!

1 Like

! REPORTING SECURITY ISSUES !

If you find a security vulnerability, please report it immediately over PMs! Security is a big concern of mine, so, finding these issues is very important.

If you find a security issue I will happily list your name in the main script (if you’d like), and generally try and provide some thanks. I may provide a bounty reward in USD or Robux depending on severity, though, I unfortunately can’t guarantee it will be very impressive (if I had a lot to give, I certainly would)

May 15th 2022 Update

  • Updated sandbox termination to take advantage of coroutine.close, rather than forcefully spinning threads until they die
  • Fixed an implementation bug for :BlacklistTree(), :ExceptTree(), and :UnblacklistTree() (I’m not sure if these become functional now, I have yet to write tests for them)
  • Classes have been restructured and modularized
  • Minor refactoring
  • Disabled the (very janky) __call metamethod for running “security tests” via pcall. Instead, use H6x.Testing:SecurityCheck(), which returns true or false. It is recommended that you implement this like shown below. The function simply runs all of H6x’s tests in “fast” mode (which primarily just skips tests which do measurements over time). If any of these tests fail, user code might not run correctly, and that could result in security issues, so you are advised not to run user code.
local H6x = require(pathToH6x)
-- If checks fail, throw an error and don't run any code
assert(H6x.Testing:SecurityCheck(), "H6x's security check failed.")

-- ...

Previously this would have looked like this:

local isSafeToUse, H6x = pcall(require(pathToH6x))
assert(isSafeToUse, "H6x's security check failed.")

Plans

  • Move development, releases, update logs, etc to GitHub (Rojo)
  • Introduce more comments, and clean up various portions of code
  • Performance & structural improvements
  • Move away from object oriented design where possible
  • Refactor class definitions (It’s a bit messy, and a bit odd how I decided to do this)
  • More diverse blacklist & whitelist mechanics
  • Refactor tests to utilize the TestEZ framework
  • Add lua/bytecode activity logging formats
  • Do not log script activity by default, instead require some sort of :Watch() and optionally :StopWatch() methods to be called (To reduce some, albeit not very impactful overhead in large/complex scripts)
  • Utilize luau typechecking

Download

H6x Prototype 5-15-22.rbxm (31.7 KB)

Legacy (OLD) downloads

H6x Prototype 5-28-21.rbxm (30.5 KB)
H6x Prototype 5-5-21 (Missing update tests).rbxm (26.6 KB)
H6x Prototype 5-3-21 B.rbxm (22.1 KB)
H6x Prototype 5-3-21 A.rbxm (18.9 KB)
H6x Prototype 5-2-21.rbxm (15.1 KB)
H6x Prototype 4-12-21.rbxm (11.9 KB)

5 Likes

Hi, is it possible to just get the raw output of a script ran? Looking for this since I need to separate different logs with each player

You can do this without this tool like so:

local loadedCode = loadstring(userCode)
local environment = getfenv()

-- Overwrite print & warn with some custom version:
setfenv(loadedCode, setmetatable({ print = customPrint; warn = customWarn; }, {
	__index = environment;
}))

But yes, you can also totally do it with this tool too, though, I wouldn’t recommend it for just that. :smile:


P.s. I was previously experimenting with trying to come up with a better structure/layout for another rewrite of this tool (I called it Chlorine and the attempt is on my GitHub) but unfortunately most of my time right now is dedicated to work so I have not been making improvements or progressing this or any other alternatives that I’ve been trying to create.

There are flaws with this tool and my other attempts that I have yet to really figure out how to properly address. The big one is that it just isn’t as easy to use as I would like, and, there is definitely a lot of performance overhead that I would love to cut down on as much as possible (not that I can eliminate it entirely).

1 Like