Skip to main content

InstanceSandboxer

AdvancedCustomization

A class for wrapping and unwrapping Instances and RBXScriptSignals.

Types

AnyFn

type AnyFn = (...any) → ...any

Function type that all functions can be reduced to.

MetamethodHook

type MetamethodHook = (
fn(
...any
) → ...any,
any,
...any
) → ...any

Type for metamethod hook functions. First argument is the original metamethod function that can be called. This function expects an unwrapped Instance as its first argument.

Second argument is the wrapped Instance. Remaining arguments are the arguments passed to the metamethod at invocation.

Properties

Instances

This item is read only and cannot be modified. Read OnlyAdvanced
InstanceSandboxer.Instances: {[Instance]true}

A set of all Instances that have been accessed in the sandbox, including those that were created by the sandbox and those that were not. This is used for tracking all Instances that may have been modified by sandboxed code, so that they can be cleaned up or handled differently if necessary.

When Config.TrackInstances is enabled, any Instance that is accessed in the sandbox will be added here. Keep in mind newly-created Instances will be added here as well, so this will also include all Instances in InstanceSandboxer.NewInstances. New Instances that were created and were found to be forbidden via InstanceList.ForbiddenClasses will not be added to this set.

To iterate through these Instances and clear them, you can do something like this:

for inst in InstanceSandboxer.Instances do
	-- Your cleanup code here, such as:
	inst:Destroy()
end

-- Instances get GC'd once destroyed, so this 
-- is sufficient for cleanup in most cases, 
-- but you can also do:
table.clear(InstanceSandboxer.Instances)

NewInstances

This item is read only and cannot be modified. Read OnlyAdvanced
InstanceSandboxer.NewInstances: {[Instance]true}

A set of Instances that have been created in the sandbox. This is used for tracking Instances that were created by sandboxed code, so that they can be cleaned up or handled differently if necessary.

When Config.TrackInstances is enabled, any Instance that is created will be added here. Keep in mind these will also be added to InstanceSandboxer.Instances, since they are also accessed in the sandbox.

To iterate through these Instances and clear them, you can do something like this:

for inst in InstanceSandboxer.NewInstances do
	-- Your cleanup code here, such as:
	inst:Destroy()
end

-- Instances get GC'd once destroyed, so this 
-- is sufficient for cleanup in most cases, 
-- but you can also do:
table.clear(InstanceSandboxer.NewInstances)

InstanceCreated

This item is read only and cannot be modified. Read OnlyAdvanced
InstanceSandboxer.InstanceCreated: RBXScriptSignal<Instance>

A signal that is fired when an Instance is created in the sandbox.

RBXScriptConnections

This item is read only and cannot be modified. Read OnlyAdvanced
InstanceSandboxer.RBXScriptConnections: {[RBXScriptConnection]true}

A set of RBXScriptConnections that have been created by the sandbox. This is used for tracking connections so that they can be cleaned up when necessary. This will only be populated if and only if Config.TrackRBXScriptConnections is enabled.

To iterate through these connections and clear them, you can do something like this:

for conn in InstanceSandboxer.RBXScriptConnections do
	conn:Disconnect()
end

-- RBXScriptConnections get GC'd once disconnected,
-- so this is sufficient for cleanup in most cases, 
-- but you can also do:
table.clear(InstanceSandboxer.RBXScriptConnections)

Functions

deepWrap

AdvancedInternal
InstanceSandboxer.deepWrap(
vany,--

The value to deeply wrap.

instanceInstance?,--

The Instance if wrapping one of its properties, methods, or events.

keystring?,--

The key of the property, method, or event being wrapped, if applicable.

freezeboolean?,--

Whether to freeze tables.

pvisitedany?--

Internal parameter, do not use.

) → any--

The deeply wrapped value.

Deeply wraps v if it is a table, wrapping Instances and RBXScriptSignals as needed. If v is an Instance or RBXScriptSignal, it will be wrapped and returned.

deepUnwrap

AdvancedInternal
InstanceSandboxer.deepUnwrap(
vany,--

The value to deeply unwrap.

freezeboolean?,--

Whether to freeze tables.

pvisitedany?--

Internal parameter, do not use.

) → any--

The deeply unwrapped value.

Deeply unwraps v if it is a table, unwrapping Instances and RBXScriptSignals as needed. If v is a wrapped Instance or RBXScriptSignal, it will be unwrapped and returned.

unwrapArgs

Internal
InstanceSandboxer.unwrapArgs(...any) → ...any

Deeply unwraps all arguments passed to the function (usually from inside the sandbox), making them ready to be used in a method call on an Instance.

wrapArgs

Internal
InstanceSandboxer.wrapArgs(...any) → ...any

Deeply wraps all arguments passed to the function, making them ready to be returned to the sandboxed environment.

wrapFn

Internal
InstanceSandboxer.wrapFn(
fnAnyFn,--

The function to wrap.

passthroughInboolean,--

Whether to passthrough arguments untouched.

passthroughOutboolean--

Whether to passthrough return values untouched.

) → AnyFn--

The wrapped function.

Wraps a function in a proxy that allows it to be safely used in the sandbox. The wrapped function will return wrapped arguments when called.

reverseWrapFn

Internal
InstanceSandboxer.reverseWrapFn(
fnAnyFn--

The function to wrap.

) → AnyFn--

The wrapped function.

Wraps a function in a proxy that allows it to be safely used outside the sandbox. This is mainly used for callbacks being added for things like BindableFunctions.

wrapEvent

Internal
InstanceSandboxer.wrapEvent(
signalRBXScriptSignal,--

The RBXScriptSignal to wrap.

namestring--

The name of the signal, used for debugging purposes and printing out to console.

) → any--

The wrapped RBXScriptSignal.

Wraps an RBXScriptSignal in a proxy that allows it to be safely used in the sandbox. The proxy will have the same methods as an RBXScriptSignal, but will return wrapped arguments when the signal is fired.

wrapInstance

Internal
InstanceSandboxer.wrapInstance(
instanceInstance,--

The Instance to wrap.

fromInstanceConstructorany?--

Internal parameter, do not use.

) → any--

The wrapped Instance.

Wraps an Instance in a proxy that allows it to be safely used in the sandbox. The proxy will have the same properties, methods, and events as the Instance, but will return wrapped arguments when accessed.

hookMetamethod

Advanced
InstanceSandboxer.hookMetamethod(
instInstance,--

Unwrapped Instance to hook.

nameMetamethod,--

Metamethod name (with or without __ prefix) to hook.

hookMetamethodHook?--

Callback invoked when the metamethod fires. Recommended to read MetamethodHook documentation for details.

) → ()

Hooks a specific metamethod on a wrapped Instance. Allows for hooking functions on Instances, modifying behaviors, or changing properties within sandboxed code.

Safety Guarantees

  • self received by the hook is always the wrapped proxy; it is never auto-unwrapped.
  • Every additional argument reaches the hook untouched; no wrapping, unwrapping, or copying occurs.
  • Return values flow straight back to the sandbox exactly as yielded by the hook; no automatic wrapping occurs.
  • The hook therefore must explicitly call InstanceSandboxer.unwrap, deepUnwrap, wrapInstance, deepWrap, or wrapArgs whenever data crosses the sandbox boundary. It is the caller's responsibility to ensure that this occurs correctly.

Behavior

  • The hook completely replaces the default handling of the selected metamethod for inst.
    • The option to revert to conditionally default behavior is available by calling the first argument, which is the original metamethod function. This function expects an unwrapped Instance as its first argument, followed by any additional arguments passed to the metamethod. It is highly recommended to passthrough arguments untouched to avoid unexpected behavior.
  • If the hook does not call the original metamethod function, the default behavior is skipped.
  • Hooks do not chain; setting a hook for a metamethod that already has one overwrites the previous hook.
  • Hooks are per-instance, not inherited.

Example

local TweenService = game:GetService("TweenService")
InstanceSandboxer.hookMetamethod(TweenService, "__index", function(originalIndex, wrapped: any, key)
	if key == "Create" then
		-- calls __index on TweenService (unwrapped!!)
		-- will return the wrapped TweenService.Create function
		local create = originalIndex(TweenService, key)
		return function(self, ...)
			-- due to Luau syntax desugaring, we must explicitly pass
			-- self. Calling Something:Func(...) is implicitly the same
			-- as calling Something.Func(Something, ...)
			local tween = create(self, ...)
			print("hello", tween, ...)
			return tween
		end
	else
		return originalIndex(TweenService, key)
	end
end)

unwrap

Basic
InstanceSandboxer.unwrap(
aany--

The wrapped Instance or RBXScriptSignal to unwrap.

) → (Instance | RBXScriptSignal)?

Unwraps a wrapped Instance or RBXScriptSignal. If the Instance or RBXScriptSignal is not wrapped, it will return nil.

isWrapped

Basic
InstanceSandboxer.isWrapped(aany) → boolean

Checks if a is a wrapped Instance.

isWrappedSignal

Basic
InstanceSandboxer.isWrappedSignal(aany) → boolean

Checks if a is a wrapped RBXScriptSignal.

Show raw api
{
    "functions": [
        {
            "name": "deepWrap",
            "desc": "Deeply wraps `v` if it is a table, wrapping `Instance`s and `RBXScriptSignal`s as needed.\nIf `v` is an `Instance` or `RBXScriptSignal`, it will be wrapped and returned.",
            "params": [
                {
                    "name": "v",
                    "desc": "The value to deeply wrap.",
                    "lua_type": "any"
                },
                {
                    "name": "instance",
                    "desc": "The `Instance` if wrapping one of its properties, methods, or events.",
                    "lua_type": "Instance?"
                },
                {
                    "name": "key",
                    "desc": "The key of the property, method, or event being wrapped, if applicable.",
                    "lua_type": "string?"
                },
                {
                    "name": "freeze",
                    "desc": "Whether to freeze tables.",
                    "lua_type": "boolean?"
                },
                {
                    "name": "pvisited",
                    "desc": "*Internal parameter, **do not use***.",
                    "lua_type": "any?"
                }
            ],
            "returns": [
                {
                    "desc": "The deeply wrapped value.",
                    "lua_type": "any"
                }
            ],
            "function_type": "static",
            "tags": [
                "Advanced",
                "Internal"
            ],
            "source": {
                "line": 215,
                "path": "src/InstanceSandboxer.luau"
            }
        },
        {
            "name": "deepUnwrap",
            "desc": "Deeply unwraps `v` if it is a table, unwrapping `Instance`s and `RBXScriptSignal`s as needed.\nIf `v` is a wrapped `Instance` or `RBXScriptSignal`, it will be unwrapped and returned.",
            "params": [
                {
                    "name": "v",
                    "desc": "The value to deeply unwrap.",
                    "lua_type": "any"
                },
                {
                    "name": "freeze",
                    "desc": "Whether to freeze tables.",
                    "lua_type": "boolean?"
                },
                {
                    "name": "pvisited",
                    "desc": "*Internal parameter, **do not use***.",
                    "lua_type": "any?"
                }
            ],
            "returns": [
                {
                    "desc": "The deeply unwrapped value.",
                    "lua_type": "any"
                }
            ],
            "function_type": "static",
            "tags": [
                "Advanced",
                "Internal"
            ],
            "source": {
                "line": 283,
                "path": "src/InstanceSandboxer.luau"
            }
        },
        {
            "name": "unwrapArgs",
            "desc": "Deeply unwraps all arguments passed to the function (usually\nfrom inside the sandbox), making them ready to be used in a\nmethod call on an `Instance`.",
            "params": [
                {
                    "name": "...",
                    "desc": "",
                    "lua_type": "any"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "...any\n"
                }
            ],
            "function_type": "static",
            "tags": [
                "Internal"
            ],
            "source": {
                "line": 339,
                "path": "src/InstanceSandboxer.luau"
            }
        },
        {
            "name": "wrapArgs",
            "desc": "Deeply wraps all arguments passed to the function, making\nthem ready to be returned to the sandboxed environment.",
            "params": [
                {
                    "name": "...",
                    "desc": "",
                    "lua_type": "any"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "...any\n"
                }
            ],
            "function_type": "static",
            "tags": [
                "Internal"
            ],
            "source": {
                "line": 358,
                "path": "src/InstanceSandboxer.luau"
            }
        },
        {
            "name": "wrapFn",
            "desc": "Wraps a function in a proxy that allows it to be safely used in the sandbox.\nThe wrapped function will return wrapped arguments when called.",
            "params": [
                {
                    "name": "fn",
                    "desc": "The function to wrap.",
                    "lua_type": "AnyFn"
                },
                {
                    "name": "passthroughIn",
                    "desc": "Whether to passthrough arguments untouched.",
                    "lua_type": "boolean"
                },
                {
                    "name": "passthroughOut",
                    "desc": "Whether to passthrough return values untouched.",
                    "lua_type": "boolean"
                }
            ],
            "returns": [
                {
                    "desc": "The wrapped function.",
                    "lua_type": "AnyFn"
                }
            ],
            "function_type": "static",
            "tags": [
                "Internal"
            ],
            "source": {
                "line": 382,
                "path": "src/InstanceSandboxer.luau"
            }
        },
        {
            "name": "reverseWrapFn",
            "desc": "Wraps a function in a proxy that allows it to be safely used outside the sandbox.\nThis is mainly used for callbacks being added for things like BindableFunctions.",
            "params": [
                {
                    "name": "fn",
                    "desc": "The function to wrap.",
                    "lua_type": "AnyFn"
                }
            ],
            "returns": [
                {
                    "desc": "The wrapped function.",
                    "lua_type": "AnyFn"
                }
            ],
            "function_type": "static",
            "tags": [
                "Internal"
            ],
            "source": {
                "line": 419,
                "path": "src/InstanceSandboxer.luau"
            }
        },
        {
            "name": "wrapEvent",
            "desc": "Wraps an `RBXScriptSignal` in a proxy that allows it to be safely used in \nthe sandbox. The proxy will have the same methods as an `RBXScriptSignal`, \nbut will return wrapped arguments when the signal is fired.",
            "params": [
                {
                    "name": "signal",
                    "desc": "The `RBXScriptSignal` to wrap.",
                    "lua_type": "RBXScriptSignal"
                },
                {
                    "name": "name",
                    "desc": "The name of the signal, used for debugging purposes and printing out to console.",
                    "lua_type": "string"
                }
            ],
            "returns": [
                {
                    "desc": "The wrapped `RBXScriptSignal`.",
                    "lua_type": "any"
                }
            ],
            "function_type": "static",
            "tags": [
                "Internal"
            ],
            "source": {
                "line": 719,
                "path": "src/InstanceSandboxer.luau"
            }
        },
        {
            "name": "wrapInstance",
            "desc": "Wraps an `Instance` in a proxy that allows it to be safely used in the sandbox.\nThe proxy will have the same properties, methods, and events as the `Instance`, \nbut will return wrapped arguments when accessed.",
            "params": [
                {
                    "name": "instance",
                    "desc": "The `Instance` to wrap.",
                    "lua_type": "Instance"
                },
                {
                    "name": "fromInstanceConstructor",
                    "desc": "*Internal parameter, **do not use***.",
                    "lua_type": "any?"
                }
            ],
            "returns": [
                {
                    "desc": "The wrapped `Instance`.",
                    "lua_type": "any"
                }
            ],
            "function_type": "static",
            "tags": [
                "Internal"
            ],
            "source": {
                "line": 760,
                "path": "src/InstanceSandboxer.luau"
            }
        },
        {
            "name": "hookMetamethod",
            "desc": "Hooks a specific metamethod on a wrapped `Instance`. Allows for hooking functions\non Instances, modifying behaviors, or changing properties within sandboxed code.\n\n### Safety Guarantees\n- `self` received by the hook is always the wrapped proxy; it is never auto-unwrapped.\n- Every additional argument reaches the hook untouched; no wrapping, unwrapping, or copying occurs.\n- Return values flow straight back to the sandbox exactly as yielded by the hook; no automatic wrapping occurs.\n- The hook therefore must explicitly call `InstanceSandboxer.unwrap`, `deepUnwrap`, `wrapInstance`,\n  `deepWrap`, or `wrapArgs` whenever data crosses the sandbox boundary. *It is the caller's responsibility to\n  ensure that this occurs correctly.*\n\n### Behavior\n- The hook completely replaces the default handling of the selected metamethod for `inst`.\n  - The option to revert to conditionally default behavior is available by calling the first argument,\n    which is the original metamethod function. This function expects an unwrapped `Instance` as its first argument,\n\tfollowed by any additional arguments passed to the metamethod. *It is highly recommended to passthrough arguments\n\tuntouched to avoid unexpected behavior.*\n- If the hook does not call the original metamethod function, the default behavior is skipped.\n- Hooks do not chain; setting a hook for a metamethod that already has one overwrites the previous hook.\n- Hooks are per-instance, not inherited.\n\n### Example\n```lua\nlocal TweenService = game:GetService(\"TweenService\")\nInstanceSandboxer.hookMetamethod(TweenService, \"__index\", function(originalIndex, wrapped: any, key)\n\tif key == \"Create\" then\n\t\t-- calls __index on TweenService (unwrapped!!)\n\t\t-- will return the wrapped TweenService.Create function\n\t\tlocal create = originalIndex(TweenService, key)\n\t\treturn function(self, ...)\n\t\t\t-- due to Luau syntax desugaring, we must explicitly pass\n\t\t\t-- self. Calling Something:Func(...) is implicitly the same\n\t\t\t-- as calling Something.Func(Something, ...)\n\t\t\tlocal tween = create(self, ...)\n\t\t\tprint(\"hello\", tween, ...)\n\t\t\treturn tween\n\t\tend\n\telse\n\t\treturn originalIndex(TweenService, key)\n\tend\nend)\n```",
            "params": [
                {
                    "name": "inst",
                    "desc": "**Unwrapped** Instance to hook.",
                    "lua_type": "Instance"
                },
                {
                    "name": "name",
                    "desc": "Metamethod name (with or without `__` prefix) to hook.",
                    "lua_type": "Metamethod"
                },
                {
                    "name": "hook",
                    "desc": "Callback invoked when the metamethod fires. Recommended to read MetamethodHook documentation for details.",
                    "lua_type": "MetamethodHook?"
                }
            ],
            "returns": [],
            "function_type": "static",
            "tags": [
                "Advanced"
            ],
            "source": {
                "line": 960,
                "path": "src/InstanceSandboxer.luau"
            }
        },
        {
            "name": "unwrap",
            "desc": "Unwraps a wrapped `Instance` or `RBXScriptSignal`.\nIf the `Instance` or `RBXScriptSignal` is not wrapped, it will return `nil`.",
            "params": [
                {
                    "name": "a",
                    "desc": "The wrapped `Instance` or `RBXScriptSignal` to unwrap.",
                    "lua_type": "any"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "(Instance | RBXScriptSignal)?\n"
                }
            ],
            "function_type": "static",
            "tags": [
                "Basic"
            ],
            "source": {
                "line": 200,
                "path": "src/InstanceList.luau"
            }
        },
        {
            "name": "isWrapped",
            "desc": "Checks if `a` is a wrapped `Instance`.",
            "params": [
                {
                    "name": "a",
                    "desc": "",
                    "lua_type": "any"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "boolean\n"
                }
            ],
            "function_type": "static",
            "tags": [
                "Basic"
            ],
            "source": {
                "line": 211,
                "path": "src/InstanceList.luau"
            }
        },
        {
            "name": "isWrappedSignal",
            "desc": "Checks if `a` is a wrapped `RBXScriptSignal`.",
            "params": [
                {
                    "name": "a",
                    "desc": "",
                    "lua_type": "any"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "boolean\n"
                }
            ],
            "function_type": "static",
            "tags": [
                "Basic"
            ],
            "source": {
                "line": 223,
                "path": "src/InstanceList.luau"
            }
        }
    ],
    "properties": [
        {
            "name": "Instances",
            "desc": "A set of all `Instance`s that have been accessed in the sandbox, \nincluding those that were created by the sandbox and those that were not. \nThis is used for tracking all `Instance`s that may have been modified by \nsandboxed code, so that they can be cleaned up or handled differently if necessary.\n\nWhen `Config.TrackInstances` is enabled, any `Instance` that is accessed \nin the sandbox will be added here. Keep in mind newly-created `Instance`s \nwill be added here as well, so this will also include all `Instance`s in \n`InstanceSandboxer.NewInstances`. New `Instance`s that were created and\nwere found to be forbidden via `InstanceList.ForbiddenClasses` will not \nbe added to this set.\n\nTo iterate through these `Instance`s and clear them, you can do something like this:\n\n```lua\nfor inst in InstanceSandboxer.Instances do\n\t-- Your cleanup code here, such as:\n\tinst:Destroy()\nend\n\n-- Instances get GC'd once destroyed, so this \n-- is sufficient for cleanup in most cases, \n-- but you can also do:\ntable.clear(InstanceSandboxer.Instances)\n```",
            "lua_type": "{ [Instance]: true }",
            "tags": [
                "Advanced"
            ],
            "readonly": true,
            "source": {
                "line": 75,
                "path": "src/InstanceSandboxer.luau"
            }
        },
        {
            "name": "NewInstances",
            "desc": "A set of `Instance`s that have been created in the sandbox.\nThis is used for tracking Instances that were created by \nsandboxed code, so that they can be cleaned up or handled \ndifferently if necessary.\n\nWhen `Config.TrackInstances` is enabled, any `Instance` that is created\nwill be added here. Keep in mind these will **also** be added to \n`InstanceSandboxer.Instances`, since they are also accessed in the sandbox.\n\nTo iterate through these `Instance`s and clear them, you can do something like this:\n\n```lua\nfor inst in InstanceSandboxer.NewInstances do\n\t-- Your cleanup code here, such as:\n\tinst:Destroy()\nend\n\n-- Instances get GC'd once destroyed, so this \n-- is sufficient for cleanup in most cases, \n-- but you can also do:\ntable.clear(InstanceSandboxer.NewInstances)\n```",
            "lua_type": "{ [Instance]: true }",
            "tags": [
                "Advanced"
            ],
            "readonly": true,
            "source": {
                "line": 106,
                "path": "src/InstanceSandboxer.luau"
            }
        },
        {
            "name": "InstanceCreated",
            "desc": "A signal that is fired when an `Instance` is created in the sandbox.",
            "lua_type": "RBXScriptSignal<Instance>",
            "tags": [
                "Advanced"
            ],
            "readonly": true,
            "source": {
                "line": 117,
                "path": "src/InstanceSandboxer.luau"
            }
        },
        {
            "name": "RBXScriptConnections",
            "desc": "A set of `RBXScriptConnection`s that have been created by the sandbox. \nThis is used for tracking connections so that they can be cleaned up\nwhen necessary. This will only be populated if and only if\n`Config.TrackRBXScriptConnections` is enabled.\n\nTo iterate through these connections and clear them, you can do something like this:\n\n```lua\nfor conn in InstanceSandboxer.RBXScriptConnections do\n\tconn:Disconnect()\nend\n\n-- RBXScriptConnections get GC'd once disconnected,\n-- so this is sufficient for cleanup in most cases, \n-- but you can also do:\ntable.clear(InstanceSandboxer.RBXScriptConnections)\n```",
            "lua_type": "{ [RBXScriptConnection]: true }",
            "tags": [
                "Advanced"
            ],
            "readonly": true,
            "source": {
                "line": 154,
                "path": "src/InstanceSandboxer.luau"
            }
        }
    ],
    "types": [
        {
            "name": "AnyFn",
            "desc": "Function type that all functions can be reduced to.",
            "lua_type": "(...any) -> ...any",
            "source": {
                "line": 193,
                "path": "src/InstanceSandboxer.luau"
            }
        },
        {
            "name": "MetamethodHook",
            "desc": "Type for metamethod hook functions.\nFirst argument is the original metamethod function that can be called. \n**This function expects an unwrapped `Instance` as its first argument.**\n\nSecond argument is the wrapped `Instance`.\nRemaining arguments are the arguments passed to the metamethod at invocation.",
            "lua_type": "(fn: (Instance, ...any) -> ...any, any, ...any) -> ...any",
            "source": {
                "line": 444,
                "path": "src/InstanceSandboxer.luau"
            }
        }
    ],
    "name": "InstanceSandboxer",
    "desc": "A class for wrapping and unwrapping `Instance`s and `RBXScriptSignal`s.",
    "tags": [
        "Advanced",
        "Customization"
    ],
    "source": {
        "line": 34,
        "path": "src/InstanceSandboxer.luau"
    }
}