[Major Issues] ProxyModule for Custom Instance Properties/Custom Userdatas/Script Sandboxing and more

Updates & Issues are located at the bottom

Introduction:

Instances, the building blocks of Roblox, we all know what Instances are. They are very unique and vary a lot on their purpose and design. Instances can be limited though, which is why I have created a module to improve the workflow of using Instances/Customizable script sandboxing/Custom userdata creation. My module allows for you to add custom properties to Instances and much more, which I will discuss below.

Development / Feedback Relations:

Currently, my module has 30+ functions to utilize for developing new and fun ways to interact with instances and userdatas on roblox. The module is not done yet, and I still plan to add more; I just feel like right now it’s stable and ready to be released. I will accept any form of feedback, and will overall improve the module with feedback I get.

Some Included Features:

For a full list of features please look through the documentation script which will be listed under the resources category. Here is a list of some of the features included inside my ProxyModule:

  • Custom Instance Property Support

You can add/edit/remove custom properties to/from Instances using my module; but there’s a slight downside to this: Attributes supersede custom properties to some extent. My custom properties are not replicated, because my module sandboxes all Instances it comes into contact with, so custom properties will only be local to the script running the module. An upside is that custom properties don’t have to be gotten using the function :GetAttribute() like attributes though. Using a specific function, you can add :WaitForChildOfClass() and :WaitForChildWhichIsA() to all Instances, a feature I’ve been wanting on roblox for awhile now.

  • Script Sandboxing Functions

My ProxyModule was designed to sandbox code inside scripts, and using it you can sandbox just about anything you want.

  • Custom DataType Support

You can create custom datatypes (userdatas) and add custom properties to them. You can change the output that typeof has on the custom datatypes too, for example:

local Roblox = ProxyModule:CreateCustomUserdata("RobloxUserdata")
print(typeof(Roblox))
--Expected Output: RobloxUserdata
  • Custom Enums

You can create your own custom Enums and EnumItems using my ProxyModule, which can be used for creating other utilities or backend systems.

Why use my ProxyModule?

My ProxyModule offers a lot of opportunities to create new games with ease. You could create a game like Lua Learning, because you can easily sandbox Instances and remove features that you don’t want players being able to access. You could create a game where players can write their own code and execute it, and one restriction that my ProxyModule offers is Player instance locking, which will prevent players from kicking/removing other players from the game (example below)

ProxyModule:LockPlayerInstances()
--After this you would run the "SetupInstanceSandbox" function, but we'll ignore that for this example
local Me = game:GetService("Players").FIREYAUTO
Me:Kick() --or Me:Destroy(),Me:Remove(), or game:GetService("Players"):ClearAllChildren(), or Me.Parent = nil, or game:GetService("Debris"):AddItem(Me,0)
--The line above would error

Code Examples:

This is my favorite code example, creating fake game services

local ProxyModule = require(script.Proxy)
ProxyModule:SaveSharedSetting("FakeServices",{})
ProxyModule:SaveSharedSetting("NewService",function(ServiceName,CustomProperties)
	local FakeServices = ProxyModule:GetSharedSetting("FakeServices")
	local Service = ProxyModule:CreateCustomUserdata("Instance")
	Service:AddProperty("Name",ServiceName,true)
	Service:AddProperty("ClassName",ServiceName,true)
	Service:SetToStringIndexName("Name")
	if CustomProperties and type(CustomProperties) == "table" then
		for k,v in pairs(CustomProperties) do
			Service:AddProperty(k,v.Value,v.ReadOnly or false)
		end
	end
	Service:DisablePropertyActions()
	FakeServices[#FakeServices+1] = Service
	ProxyModule:AddClassModifier("DataModel",ServiceName,Service)
end)
ProxyModule:SaveOverrideFunction("GetService",function(i,ServiceName)
	for _, FakeService in pairs(ProxyModule:GetSharedSetting("FakeServices")) do
		if FakeService.ClassName == ServiceName then
			return FakeService
		end
	end
	return i:GetService(ServiceName)
end)
ProxyModule:SaveOverrideFunction("FindService",function(i,ServiceName)
	for _, FakeService in pairs(ProxyModule:GetSharedSetting("FakeServices")) do
		if FakeService.ClassName == ServiceName then
			return FakeService
		end
	end
	return i:FindService(ServiceName)
end)
ProxyModule:AddClassModifier("ServiceProvider","GetService",ProxyModule:GetOverrideFunction("GetService"))
ProxyModule:AddClassModifier("ServiceProvider","getService",ProxyModule:GetOverrideFunction("GetService"))
ProxyModule:AddClassModifier("ServiceProvider","service",ProxyModule:GetOverrideFunction("GetService"))
ProxyModule:AddClassModifier("ServiceProvider","findService",ProxyModule:GetOverrideFunction("FindService"))
ProxyModule:AddClassModifier("ServiceProvider","FindService",ProxyModule:GetOverrideFunction("FindService"))
ProxyModule:CustomEnum("OutputType",{Print=0,Warn=1,Error=2})
ProxyModule:GetSharedSetting("NewService")("PrintService",{
	Log={
		Value=function(i,m,t)
			if i.ClassName ~= "PrintService" then
				return
			else
				if not t or ProxyModule:ValueEqualsEnum(t,Enum.OutputType.Print) then
					return print(m)
				elseif ProxyModule:ValueEqualsEnum(t,Enum.OutputType.Warn) then
					return warn(m)
				elseif ProxyModule:ValueEqualsEnum(t,Enum.OutputType.Error) then
					return error(m)
				end
			end
		end,
		ReadOnly=true
	},
})
ProxyModule:SetupInstanceSandbox({"workspace","Workspace","game","Game","script"})
ProxyModule:SandboxEnum()

--The part we've been waiting for, using the service
game:GetService("PrintService"):Log("Hello, World!",Enum.OutputType.Print)

Custom Instance Properties

local ProxyModule = require(script.Proxy)
ProxyModule:AddFunMethods()
ProxyModule:AddClassNameModifier("DataModel","ReverseString",function(i,v)
	if i.ClassName ~= "DataModel" then return end
	print(string.reverse(v))
end)
ProxyModule:AddClassModifier("BasePart","FakeName","Fake BasePart Name")
ProxyModule:SetupInstanceSandbox({"workspace","Workspace","game","Game","script"})

wait(3)

local Part1 = Instance.new("Part",workspace)
Part1:Explode()

local Part2 = Instance.new("Part",workspace)
print("Part2.FakeName",Part2.FakeName) --Part2.FakeName Fake BasePart Name

local Part3 = Instance.new("Part",workspace)
Part3:AddCustomProperty("IsUserdata",true)
print("Part3.IsUserdata",Part3.IsUserdata) --Part3.IsUserdata true
Part3:AddPropertyOverride("Name","Not Actual Part Name")
print("Part3.Name",Part3.Name) --Part3.Name Not Actual Part Name

game:ReverseString("Roblox is cool!") --!looc si xolboR

The last thing I’d like to showcase what my ProxyModule can do is a custom userdata example

local ProxyModule = require(script.Proxy)
ProxyModule:SetupInstanceSandbox({"workspace","Workspace","game","Game","script"})

local Roblox = ProxyModule:CreateCustomUserdata("Roblox")

print(typeof(Roblox)) --Roblox
Roblox:AddProperty("IsCool",true)
print(Roblox.IsCool) --true
Roblox:SetReadOnly("IsCool")

Roblox:AddProperty("Hidden","I am hidden")
print(Roblox.Hidden) --I am hidden
Roblox:SetHidden("Hidden")
print(Roblox.Hidden) --nil

Roblox:AddProperty("Value","ProxyModule Property!")
Roblox:SetReadOnly("Value")

Roblox:SetToStringIndexName("Value")

print(Roblox) --ProxyModule Property!

Roblox:DisablePropertyActions() --Now you will not be able to add/remove properties from the Roblox userdata

Resources:

  • ProxyModule (Non-Require). This is the non-require version of the Proxy Module, if you want to be able to edit the code inside it for your own development then I’d recommend that you use this, as it doesn’t auto-update.

https://www.roblox.com/library/6284650254/ProxyModule

  • ProxyModule (Require). This is the require version of the Proxy Module. This one is designed to be auto-updating, but you can’t edit it unless you make a copy of it and upload it to Roblox.

https://www.roblox.com/library/6284643137/Proxy-MainModule

  • ProxyModule Documentation. This is the script that has all of the documentation for the functions, and goes into enough detail (I hope) that you can understand how to use the them for developing.

https://www.roblox.com/library/6283981569/ProxyModule-Documentation

Current Limitations:

  • There are most likely ways to bypass the Instance sandbox.

  • Client replicated properties; a feature that might never be possible.

  • Memory Issues

Notes:
The documentation script also has my discord tag if you’d like to contact me for suggestions or bugs. I will be updating this module when I find a bug or think of a new feature to include.

Updates: (Updates that are 2 versions below the current version will not be listed)

  • v0.0.4: The stats global is now sandboxed and will no longer allow an instance sandbox bypass. There is a new function to sandbox the require global so it can be used with the instance sandbox. Fixed ProxyModule:LockPlayerInstances(), there was a vulnerability that allowed you to bypass and remove players from the game. I have added a new feature to lock instances so they cannot be accessed while in the sandboxed environment. The documentation has a list of the new functions I’ve added.
  • v0.0.3: I have fixed the issue with BindableFunctions and RemoteFunctions, previously it wouldn’t sandbox the results from when they were invoked, it will do this now. (This may not work under some circumstances, if you encounter a bug with this feature please let me know)

Issues:

  • Major issues have been discovered related to the sandbox. The getfenv global allows an easy bypass, this will lead me to rewrite this entire module.
  • CachedFunctions are recreated even though roblox uses the same functions for the same methods on different Instances. Example:
print(game.Destroy == workspace.Destroy)
--Expected Output: true

In my sandbox, this would print false because it creates a new function for each method in an Instance. This is also another reason why I must rewrite this module. A rewrite for this module will take some time, use the current version of this utility at your own risk.

13 Likes

for LockPlayerInstances, do you lock some services too? because you can do Debris:AddItem(Player, 1).
Also, you might want to lock the camelCase equivalents, too:
image

One thing I definitely dont understand is how you would sandbox crash loops ect from server, unless you explicitly create a custom lua bytecode interpreter to detect some of them (and even then, you can still bypass it).

1 Like

No, it doesn’t lock services. I didn’t even think of locking Debris:AddItem, I’ll have to add my FakeServices example into the actual script to proxy Debris to prevent this from happening. This should be fixed in the next update. Thank you for commenting about this issue.

Also, no, the script cannot stop loops that can slow/destroy servers.

1 Like