Security
Sandboxer is designed to restrict untrusted code from accessing sensitive APIs and data. It is not designed to, for example, prevent crashes from while true do end.
Sandboxer can easily block accesses like:
game:GetService("DataStoreService"):GetDataStore("MyDataStore"):SetAsync("MyKey", "funny")
But it cannot prevent code like:
while true do
print("Hello!")
end
It can prevent the print, but it will not prevent the infinite loop and execution will continue until terminated due to a timeout or crash.
Code can otherwise cause crashes. For example, one could infinitely allocate buffers:
local buffers = {}
for _ = 1, 100000 do
table.insert(buffers, buffer.create(100000))
end
This is a denial-of-service attack, and Sandboxer is not designed to prevent this. Sandboxer does not attempt to limit CPU, memory, or yield behavior. It is up to the developer to implement their own resource limits for untrusted code.
Guarantees
Sandboxer makes some guarantees when data is not intentionally leaked from trusted code to sandboxed code.
Trusted code refers to any code executed outside the Sandboxer environment, including modules that construct or configure the sandbox.
Sandboxer is designed so that, in its intended configuration, no known bypasses exist that allow access to restricted APIs without explicit leakage from trusted code.
It is very possible that someone could do this in trusted code:
Sandboxer.EditDefaultSandbox({
game = function() return game end
})
Now, the actual Instance tree is leaked and nothing can be done to secure it.
This is not a vulnerability, it is behavior caused by usage.
If you find a security vulnerability or sandbox escape with the unmodified version of the module, please do not hesitate to DM me on DevForum or on Discord (littlebitsman)! Please do not post vulnerabilities publicly.
The security guarantees are as follows, assuming no leakage of data from trusted code to sandboxed code and all sandboxed code is executed in a single Luau VM (i.e., no cross-VM communication using Actors):
- Wrapped Instances never expose the underlying Instance or its metatable. Any Instance or RBXScriptSignal delivered to sandboxed code via a wrapped Instance is automatically wrapped before being exposed.
- Wrapped methods never expose raw Instances, raw RBXScriptSignals, or the underlying metatable. Any Instance or RBXScriptSignal returned from a wrapped method is automatically wrapped before being returned.
- Wrapped signals will never expose the underlying RBXScriptSignal, its metatable, any Instances returned by said signal, or any other RBXScriptSignals returned by said signal.
- All arguments delivered in a signal are wrapped before being delivered to the sandboxed code.
- Only Instances explicitly allowed in the
InstanceListwill be accessible to sandboxed code.- A blacklist (
ExplicitDisallow) exists for more granular control.
- A blacklist (
- The following globals are forbidden in sandboxed code:
getfenv,setfenv,loadstring,debug.info,debug.traceback,debug.- The only exception is if a user bypasses the checks that prevent this; read here for more information.
- Metatables on wrapped Instances and RBXScriptSignals are locked & non-referencable and cannot be accessed, replaced, or modified by sandboxed code
- Wrapper identity is preserved. Each underlying Instance or RBXScriptSignal has exactly one corresponding wrapper within a Luau VM. Wrapping the same object multiple times will always return the same wrapper.
- Two wrapped objects compare equal if and only if they wrap the same underlying
InstanceorRBXScriptSignal. - Wrapped objects are stable and may be used as table keys.
- Two wrapped objects compare equal if and only if they wrap the same underlying
- An
Instancewill not be garbage collected while a wrapper still exists within any code (not in Sandboxer) and is still reachable. - All callbacks crossing the sandbox boundary are wrapped:
- Arguments passed to sandboxed code are wrapped before being delivered.
- Wrapped arguments returned from sandboxed code are unwrapped before being delivered to trusted code.
- The following exceptions apply (i.e., one or both of the guarantees above do not apply):
- Custom wrappers created by trusted code using
InstanceSandboxer.wrapFnwith passthroughs configured - All functions in the following globals:
bit32,buffer,coroutine,math,os,string,table,task,utf8,vector,Axes,BrickColor,CatalogSearchParams,Color3,ColorSequence,ColorSequenceKeypoint,Content,DateTime,DockWidgetPluginGuiInfo,Faces,FloatCurveKey,Font,NumberSequence,NumberSequenceKeypoint,OverlapParams,Path2DControlPoint,PathWaypoint,PhysicalProperties,Random,Ray,RaycastParams,Rect,Region3,Region3int16,RotationCurveKey,TweenInfo,UDim,UDim2,Vector2,Vector2int16,Vector3,Vector3int16
- Custom wrappers created by trusted code using
- All sandboxes receive new copies of
_G/shared, instantiated as{}._G/sharedis not shared between sandboxes or other trusted code. typeofon wrapped Instances/RBXScriptSignals within the sandbox will return the same value astypeofon the underlying object in trusted code, unlesstypeofis modified or overriden. Natively, though, this will hold true.requiremay only access Instances allowed inInstanceList, even when using string paths. Only resolved targets in string paths are checked against theInstanceList. For example, ifrequire("@game/ReplicatedStorage/MyModule")is called, onlyMyModuleis checked against theInstanceList. The rest of the path is ignored.InstanceListis the sole source of truth for which Instances are allowed in the sandbox. If an Instance is not explicitly allowed, it will not be accessible to sandboxed code. Even if Instances are accessible via references/method returns/properties, they will be checked against theInstanceListbefore being delivered to sandboxed code. If an Instance is not allowed, it will be replaced withnilin the sandbox.
What's not guaranteed
- Sandboxer does not attempt to limit CPU, memory, or yield behavior. It is up to the developer to implement their own resource limits for untrusted code.
- Security of Instances if they are leaked from trusted code to sandboxed code. For example, if a trusted function returns
gameto sandboxed code, the sandbox is effectively bypassed and the guarantees above no longer hold.